CF337D Book of Evil 换根树形DP

稍微复杂的换根 DP,我能一发 A 掉的还是不多的...

题目大意

给出一棵有 \(n\) 个结点的树,其中 \(m\) 个结点 \(p_1,p_2,\cdots,p_m\) 作特殊标记,令 \(dis(x,y)\) 代表结点 \(x\)\(y\) 简单路径上边数,求有多少个点 \(x\) ,满足 \(\forall i \in [1,m] ,\max\{dis(x,p_i)\} \le d\)


题解

不妨令 \(1\) 为根。

考虑结点 \(x\) 的最远标记点,可以在 \(x\) 的子树 \(T(x)\) 内,也可以在 \(x\) 的子树外。

对这两种情况分别讨论。

\(\texttt{Part I}\)

\(x\) 的子树内情况比较简单,可以通过一次 dfs 求出。

\(dp(x)\)\(T(x)\) 内最远标记点到 \(x\) 的距离,如果 \(T(x)\) 内没有标记点,则 \(dp(x) = +\infty\)

但在这个 dfs 过程中,还要维护\(T(x)\) 内第二远标记点到 \(x\) 的距离 \(dp2(x)\),原因将在 \(\texttt{Part II}\) 解释。

\(\texttt{Part II}\)

\(x\) 的子树外情况比较复杂,但可以自然地想到是一个换根 DP:根 \(1\) 号点不存在子树外情况,已经可以求解。

假设 \(y\)\(x\) 的任一儿子,此时对 \(y\) 有两种情况:

  • \(dp(x) = dp(y) + 1\) ,即 \(dp(x)\)\(dp(y)\) 转移而来
  • \(dp(x) \neq dp(y) + 1\) ,即 \(dp(x)\) 不是由 \(dp(y)\) 转移而来。

以题目样例为例

其中 \(1\) 号点只有一个儿子 \(5\) 号点,那么 \(5\) 号点符合上述的第一种情况,也就是说,\(1\) 号点的最远点就在孩子 \(5\) 号子树内。

此时,如果从 \(1\) 号点向 \(5\) 号点换根,如果使用 \(dp(1)\),则 \(5\) 号点子树外的情况并未得到考虑,所以还需要记录 \(\texttt{Part I}\) 中所述的 \(dp2(x)\) ,并在换根过程中分类讨论。

其他细节请见代码。


\(\texttt{Code}\)

#include<bits/stdc++.h>
using namespace std;

typedef long long LL;

template < typename Tp >
void read(Tp &x) {
	x = 0; int fh = 1; char ch = 1;
	while(ch != '-' && (ch < '0' || ch > '9')) ch = getchar();
	if(ch == '-') fh = -1, ch = getchar();
	while(ch >= '0' && ch <= '9') x = x * 10 + ch - '0', ch = getchar();
	x *= fh;
}

const int maxn = 100000 + 7;
const int maxm = 200000 + 7;
const int INF = 0x3f3f3f3f;

int n, m, d, dp[maxn], dp2[maxn];
bool mark[maxn];

int Head[maxn], Next[maxm], to[maxm], tot;
void addedge(int x, int y) {
	to[++tot] = y, Next[tot] = Head[x], Head[x] = tot;
}
void add(int x, int y){
	addedge(x, y); addedge(y, x);
}

void Init(void) {
	read(n); read(m); read(d);
	for(int i = 1, x; i <= m; i++) {
		read(x); mark[x] = true;
	}
	for(int i = 1, x, y; i < n; i++) {
		read(x); read(y);
		add(x, y);
	}
}

void dfs(int x, int fa) {
	dp[x] = dp2[x] = INF;
	for(int i = Head[x] ;i ; i = Next[i]) {
		int y = to[i];
		if(y == fa) continue;
		dfs(y, x);
		if(dp[y] != INF) {
			if(dp[x] == INF) dp[x] = dp[y] + 1;
			else {
				if(dp[y] + 1 > dp[x]) {
					dp2[x] = dp[x];
					dp[x] = dp[y] + 1;
				}
				else if(dp2[x] == INF) dp2[x] = dp[y] + 1;
				else dp2[x] = max(dp2[x], dp[y] + 1);
			}
		}
	}
	if(dp[x] == INF) if(mark[x]) dp[x] = 0;
}

int ans;

void dfs2(int x, int fa, int mov) {
	if(mov == INF && mark[fa]) mov = 1;
//	printf("id = %d, fa = %d, mov = %d, dp[x] = %d\n", x, fa, mov, dp[x]);
	if((dp[x] <= d || dp[x] == INF) && (mov <= d || mov == INF)) {
		++ans;
	}
	for(int i = Head[x]; i; i = Next[i]) {
		int y = to[i];
		if(y == fa) continue;
		if(dp[x] == dp[y] + 1) {
			if(mov == INF && dp2[x] != INF) dfs2(y, x, dp2[x] + 1);
			else if(mov != INF && dp2[x] == INF) dfs2(y, x, mov + 1);
			else if(mov == INF && dp2[x] == INF) dfs2(y, x, INF);
			else dfs2(y, x, max(dp2[x], mov) + 1);
		}
		else {
			if(mov == INF && dp[x] != INF) dfs2(y, x, dp[x] + 1);
			else if(mov != INF && dp[x] == INF) dfs2(y, x, mov + 1);
			else if(mov == INF && dp[x] == INF) dfs2(y, x, INF);
			else dfs2(y, x, max(dp[x], mov) + 1);
		}
	}
}

void Work(void) {
	dfs(1, 0);
//	for(int i = 1; i <= n; i++) {
//		printf("%d : %d\n", i, dp[i]);
//	}
	dfs2(1, 0, INF);
	printf("%d\n", ans);
}

int main(void) {
	Init();
	Work();
	return 0;
}
posted @ 2020-08-24 00:03  览遍千秋  阅读(146)  评论(0编辑  收藏  举报