洛谷P3267.侦察守卫


题目大意

一颗 \(n(1\leq n\leq 5\times 10^5)\) 个节点的树,在某一点 \(i\) 花费 \(w_{i}(w_{i}\leq 1000)\) 放置一个侦察守卫后可以监视到所有到 \(i\) 的距离 \(\leq d(d\leq 20)\) 的点, 有 \(m(m\leq n)\)个给定的关键节点需要监视,求能够监视到全部 \(m\) 个节点所需要的最小花费。

思路

\(f[i,x]\) 为监视到以 \(i\) 为根的子树中的全部关键点,并且可以向上监视到距离不小于 \(i\) 的节点的最小花费; \(g[i,x]\) 为至少监视到了以 \(i\) 为根的子树中的全部的与 \(i\) 的距离 \(\geq x\) 的关键点所需要的最小花费。在合并子树的过程中,对于 \(i\) 的每一个儿子 \(j\) ,我们可以得到:

\[f[i,x]=min(f[i,x]+g[j,x],g[i,x+1]+f[j,x+1]) \]

\[g[i,x]=g[i,x]+g[j,x-1] \]

此外,由我们对状态的定义,显然有:

\[f[i,x]=min(f[i,x],f[i,x+1]) \]

\[g[i,x]=min(g[i,x],g[i,x-1]) \]

对于初始值,一开始先将所有 \(f\) 初始为 \(inf\) 。考虑到每一个子树一开始只有一个根节点 \(i\) ,于是,如果 \(i\) 是关键点, 那么 \(g[i,0]=f[i,0]=w_{i}\) ,否则, \(g[i,0]=f[i,0]=0\) ,因为此时 \(g\)\(f\) 都恰好表示仅考虑节点 \(i\) 时的情况。如果 \(x\neq 0\) ,因为要向上监视,所以此处必须有一个侦察守卫,所以对于所有 \(1\leq x\leq d\)\(f[i,x]=w_{i}\)
在每次与一个子树合并时,要先计算 \(f\) ,后计算 \(g\) 。因为 \(f\) 的转移方程中需要用到一个尚未与新的子树合并的 \(g\) 。此外由于 \(g[i,0]\) 不能通过转移方程得到,每次合并时计算完 \(f\) 后,需要令 \(g[i,0]=f[i,0]\) 。复杂度 \(O(nd)\)

代码

#include
#include
#include
using namespace std;
typedef long long LL;
typedef unsigned long long ULL;
typedef pair PII;
#define all(x) x.begin(),x.end()
//#define int LL
//#define lc p*2+1
//#define rc p*2+2
#define endl '\n'
#define inf 0x3f3f3f3f
#define INF 0x3f3f3f3f3f3f3f3f
#pragma warning(disable : 4996)
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
const double eps = 1e-8;
const LL MOD = 1000000007;
const LL mod = 998244353;
const int maxn = 500010;

vectorG[maxn];
int N, D, M, W[maxn];
bool key[maxn];
int f[maxn][25], g[maxn][25];

void add_edge(int from, int to)
{
	G[from].push_back(to);
	G[to].push_back(from);
}

void dfs(int v, int p)
{
	if (key[v])
		f[v][0] = g[v][0] = W[v];
	else
		f[v][0] = g[v][0] = 0;
	for (int i = 1; i <= D; i++)
		f[v][i] = W[v];
	for (int i = 0; i < G[v].size(); i++)
	{
		int to = G[v][i];
		if (to == p)
			continue;
		dfs(to, v);
		for (int j = 0; j <= D; j++)
			f[v][j] = min(f[v][j] + g[to][j], f[to][j + 1] + g[v][j + 1]);
		for (int j = D; j >= 0; i--)
			f[v][j] = min(f[v][j], f[v][j + 1]);
		g[v][0] = f[v][0];
		for (int j = 1; j <= D; j++)
			g[v][j] += g[to][j - 1];
		for (int j = 1; j <= D; j++)
			g[v][j] = min(g[v][j], g[v][j - 1]);
	}
}

void solve()
{
	int ans = inf;
	dfs(1, 0);
	for (int i = 0; i <= D; i++)
		ans = min(f[1][i], ans);
	cout << ans << endl;
}

int main()
{
	IOS;
	memset(f, inf, sizeof(f));
	cin >> N >> D;
	for (int i = 1; i <= N; i++)
		cin >> W[i];
	cin >> M;
	int b;
	for (int i = 1; i <= M; i++)
	{
		cin >> b;
		key[b] = true;
	}
	int u, v;
	for (int i = 1; i < N; i++)
	{
		cin >> u >> v;
		add_edge(u, v);
	}
	solve();

	return 0;
}