• 【题解】P4228 [清华集训2017] 榕树之心


    link

    题意

    给定一棵有根树,初始时核心在 1 1 1 处, 1 1 1 为标记点。每次操作选择一个标记点的邻接点,将其标记,将核心往该点的方向移动一步。求 n − 1 n-1 n1 次操作后,核心是否可能落在 i i i 上。 n ≤ 1 0 5 n\le 10^5 n105

    题解

    观察发现,一些子树内部可以两两抵消。即操作若干次后会回到根节点。当核心在 i i i 上,两个节点分属于点 i i i 的不同子树时,选择这两个节点后,核心会回到 i i i

    考虑子任务 3,即只求根节点的答案。

    f i f_i fi 表示仅考虑 i i i 的子树,操作完子树后,核心离 i i i 的最近距离。

    对于点 i i i,仅考虑其子树,最优策略是用剩下的子树与重儿子两两配对消除。若不能完全消除,其正确性显然。若可以完全消除,有最优策略:每次将重儿子中的点与其它子树中深度最大的点配对,剩下的点两两配对。这样 f i f_i fi 不超过 1 1 1

    设点 i i i 的重儿子为 s s s,点 i i i 的子树大小为 s z i sz_i szi

    考虑转移:

    • 若重儿子可以被完全消除,即 f s + 1 ≤ s z i − s z s − 1 f_s+1\le sz_i-sz_s-1 fs+1sziszs1,此时核心离 i i i 的距离为 f i + 1 f_i+1 fi+1,剩下的子树有 s z i − s z s − 1 sz_i-sz_s-1 sziszs1 个点,有 f i = ( s z i − 1 )   m o d   2 f_i=(sz_i-1)\bmod 2 fi=(szi1)mod2
    • 若重儿子不能被完全消除,有 f i = f s + 1 − ( s z i − s z s − 1 ) f_i=f_s+1-(sz_i-sz_s-1) fi=fs+1(sziszs1)

    此时得到了根节点的答案。

    接下来考虑换根 dp。

    从根节点到点 i i i 的路径上的点看作已经选过了,直接无视。仅需要考虑它们的子树的影响。

    对于路径上的节点,将它们的子树当作 i i i 的子树考虑。于是在换根的过程中,只需要多记录一下路径上的重儿子即可。

    但是,当路径上的点同时又是重儿子时,会发生冲突。选次重即可。

    考虑正确性。当我们将这些点两两配对的时候,假设它们是 ( u , v ) (u,v) (u,v) d e p u < d e p v dep_udepu<depv,那么在路径跑到 v v v 的父亲处执行这对操作可以抵消。

    实现

    注意算答案时的重儿子与向下换根的重儿子是有一点不一样的。

    #include 
    using namespace std;
    const int N = 100005;
    int type, T, n, cnt = 0, fir[N], nxt[N << 1], to[N << 1], fa[N], f[N], s1[N], s2[N], sz[N], ans[N];
    template<typename Ty> void read(Ty &x) {
    	int c = getchar(), f = 1;
    	for (; c < '0' || c > '9'; c = getchar()) if (c == '-') f = -1;
    	for (x = 0; c >= '0' && c <= '9'; c = getchar()) x = (x << 1) + (x << 3) + (c ^ 48);
    	x *= f;
    }
    void ade(int u, int v) {
    	cnt++, nxt[cnt] = fir[u], fir[u] = cnt, to[cnt] = v;
    	cnt++, nxt[cnt] = fir[v], fir[v] = cnt, to[cnt] = u;
    }
    void dfs1(int r) {
    	sz[r] = 1;
    	for (int i = fir[r]; i; i = nxt[i])
    		if (to[i] != fa[r]) {
    			fa[to[i]] = r, dfs1(to[i]), sz[r] += sz[to[i]];
    			if (sz[to[i]] >= sz[s1[r]]) s2[r] = s1[r], s1[r] = to[i];
    			else if (sz[to[i]] > sz[s2[r]]) s2[r] = to[i];
    		}
    	if (sz[r] == 1) f[r] = 0;
    	else if (f[s1[r]] + 1 <= sz[r] - sz[s1[r]] - 1) f[r] = (sz[r] - 1) & 1;
    	else f[r] = f[s1[r]] + 1 - (sz[r] - sz[s1[r]] - 1);
    }
    int szmx(int x, int y) { return sz[x] > sz[y] ? x : y; }
    void dfs2(int r, int mx, int sum) {//mx为路径上的最重儿子,sum表示路径上的子树总和
    	if (r != 1) {
    		int siz = sz[r] + sum, tmp = szmx(mx, s1[r]);//这里一定要再开一个变量tmp,否则可能会和下面的路径最重儿子mx冲突
    		if (f[tmp] + 1 <= siz - sz[tmp] - 1) ans[r] = (siz - 1) & 1;
    		else ans[r] = f[tmp] + 1 - (siz - sz[tmp] - 1);
    	}
    	for (int i = fir[r], v; i; i = nxt[i])
    		if (to[i] != fa[r]) {
    			if (to[i] == s1[r]) v = s2[r];
    			else v = s1[r];
    			dfs2(to[i], szmx(mx, v), sum + sz[r] - sz[to[i]] - 1);
    		}
    }
    int main() {
    	read(type), read(T);
    	while (T--) {
    		memset(fir, 0, sizeof(fir)), memset(nxt, 0, sizeof(nxt)), cnt = 0, memset(s1, 0, sizeof(s1)), memset(s2, 0, sizeof(s2));
    		read(n);
    		for (int i = 1, u, v; i < n; i++) read(u), read(v), ade(u, v);
    		dfs1(1), ans[1] = f[1];
    		if (type == 3) { printf("%d\n", ans[1] == 0); continue; }
    		dfs2(1, 0, 0);
    		for (int i = 1; i <= n; i++) printf("%d", ans[i] == 0);
    		printf("\n");
    	}
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54

    启示

    • 像这样奇怪的树上移动问题,有可能跟重儿子相关,可以考虑将重儿子与其它子树做区分
    • 树上的移动问题,可以考虑换根 dp,要看到换根前后问题的统一性,考虑用同样的思路解决问题
  • 相关阅读:
    CSAPP bomblab
    Tomcat部署及优化
    分治算法详解
    邮件标题是邮件营销的第一生产力
    element-ui输入框如何实现回显的多选样式?
    【漏洞复现】CRMEB开源商城v5.2.2——ProductController.php——SQL注入
    完全免费的PDF软件
    【基础篇】四、本地部署Flink
    【无标题】
    【Vue3+Tres 三维开发】02-Debug
  • 原文地址:https://blog.csdn.net/inferior_hjx/article/details/132818536