1 条题解

  • 0
    @ 2025-8-24 23:01:19

    自动搬运

    查看原文

    来自洛谷,原作者为

    avatar JHR100330
    蚀尽年华

    搬运于2025-08-24 23:01:19,当前版本为作者最后更新于2025-05-20 10:08:40,作者可能在搬运后再次修改,您可在原文处查看最新版

    自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多

    以下是正文


    题目大意

    可以任意进行多次操作,每次可以选择一个叶子节点 vv,将 vv 到根的简单路径中的一条边删掉,再在 vv 与根之间连一条边,问最终树的直径最长为多少。

    分析

    一开始本人考虑每个子树内的最长链,最后找到根节点的两个最大子树求和,但是两条链可能在同一个子树内,这也是这道题的难点。

    我们分步考虑:

    1. 不做操作:答案是树的直径。
    2. 只进行 11 次操作:选一条从叶子出发的、不经过根节点的路径,删除路径上深度最小的点的父亲边,然后将其挂在根节点的下方。再选另一条从根节点出发的路径拼成答案(注意:两条路径不能相交,否则找不到合法的可以删除的边)。
    3. 进行 22 次操作:等价于选择了两条不交的、且不经过根节点的路径分别挂在根节点下方,答案为选择的两条路径长度之和加上 22
    4. 进行 33 次及以上的操作是没有意义的,因为不可能存在一条路径同时覆盖了三条新增的边。
    5. 不做操作和只做一次操作的情况下,也可以认为是选择了两条不交且不经过根节点的路径。可以统一用树形 DP 来做。

    img1


    目标

    在以 uu 为根的子树内,划分出两条没有交点的长链。

    状态

    d[u] 表示 uu 子树中端点为 uu 的最大链长。

    f[u] 表示 uu 子树中端点任意的最大链长。

    g[u] 表示 uu 子树中 22 条链的最大链长和,其中 11 条链端点为 uu

    h[u] 表示 uu 子树中 22 条链的最大链长和,并且 22 条链端点任意。

    D[u][3] 表示端点为儿子 vv 的最大、次大、三大链长。

    S[u][3] 表示对应的三个儿子的编号。

    状态转移

    1. uu 子树中端点为 uu 的最大链长:d[u]=max(d[u],d[v]+1)
    2. uu 子树中端点任意的最大链长:
      1. vv 子树继承:f[u]=max(f[u],f[v])
      2. 经过 uu 点:f[u]=max(f[u],D[u][0]+D[u][1]+2)

    img2


    1. uu 子树中最大链长和,其中一条端点为 uu
      1. vv 子树继承:g[u]=max(g[u],g[v]+1)
      2. uu 为端点不过 vv 的最长链与 vv 中的任意最长链拼凑:g[u]=max(g[u],f[v]+(s[u][0]==v?D[u][1]:D[u][0])+1)

    img3


    1. uu 子树中最大链长和,22 条链端点任意:
      1. vv 子树继承:h[u]=max(h[u],h[v])
      2. 22 条链来自不同子树:h[u]=max(h[u],f1+f2)
      3. 11 条来自 vv11 条过 uuh[u]=max(h[u],f[v]+res+2)
      4. 11 条来自 vv11 条过 u,vu,vh[u]=max(h[u],g[v]+(S[u][0]==v?D[u][1]:D[u][0])+2)

    img4


    1. 由于链不能经过根节点 11,需要对根的每个子树分别讨论并计算答案,注意加上新建的两条边的贡献:
      1. 子树之内的 22 条链长和,用 h[i] 计算。
      2. 子树之间的 22 条链长和,用 f[i] 拼凑。

    img5


    这道题也就迎刃而解了。

    AC Code:

    #include<bits/stdc++.h>
    
    using namespace std;
    
    #define ll long long
    #define PII pair<int, int>
    #define PLL pair<ll, ll>
    
    const ll inf = 2e9, N = 200050, M = 2050, MOD = 1e9 + 7;
    
    ll t, n, u, ans, mx1, mx2; 
    ll d[N], f[N], g[N], h[N];
    ll D[N][3], S[N][3];
    vector<ll> e[N]; 
    
    // 更新最大,次大,三大儿子 分类讨论 
    void upd(ll x, ll y, ll dep){
    	if(dep > D[x][0]){
    		D[x][2] = D[x][1], S[x][2] = S[x][1];
    		D[x][1] = D[x][0], S[x][1] = S[x][0];
    		D[x][0] = dep, S[x][0] = y;
    	}
    	else if(dep > D[x][1]){
    		D[x][2] = D[x][1], S[x][2] = S[x][1];
    		D[x][1] = dep, S[x][1] = y;
    	}
    	else if(dep > D[x][2]) D[x][2] = dep, S[x][2] = y;
    }
    
    // 树形DP 
    void dfs(ll now){
    	ll f1 = 0, f2 = 0;
    	for(ll i : e[now]){
    		dfs(i);
    		// 从v子树继承 
    		d[now] = max(d[now], d[i] + 1);
    		upd(now, i, d[i]);
    		f[now] = max(f[now], f[i]);
    		g[now] = max(g[now], g[i] + 1);
    		h[now] = max(h[now], h[i]);
    		// 保存两个最佳子树,便于更新h 
    		if(f[i] > f1) f2 = f1, f1 = f[i];
    		else if(f[i] > f2) f2 = f[i];
    	}
    	// 从两个最佳子树更新 
    	f[now] = max(f[now], D[now][0] + D[now][1] + 2);
    	h[now] = max(h[now], f1 + f2);
    	// 子树遍历完后再更新 
    	for(int i : e[now]){
    		g[now] = max(g[now], f[i] + (S[now][0] == i ? D[now][1] : D[now][0]) + 1);
    		ll res = 0, cnt = 0;
    		if(S[now][0] != i) res += D[now][0], cnt ++;
    		if(S[now][1] != i) res += D[now][1], cnt ++;
    		if(cnt < 2 && S[now][2] != i) res += D[now][2];
    		h[now] = max(h[now], f[i] + res + 2);
    		h[now] = max(h[now], g[i] + (S[now][0] == i ? D[now][1] : D[now][0]) + 2);
    	}
    }
    
    int main(){
    	scanf("%lld", &t);
    	while(t --){
    		scanf("%lld", &n);
    		// 记得全部清空!!! 
    		for(int i = 1; i <= n; i ++){
    			e[i].clear();
    			S[i][0] = S[i][1] = S[i][2] = f[i] = d[i] = h[i] = 0;
    			D[i][0] = D[i][1] = D[i][2] = g[i] = -1;
    		}
    		for(int i = 2; i <= n; i ++){
    			scanf("%lld", &u);
    			e[u].push_back(i);
    		}
    		dfs(1);
    		// 统计最大链,次大链之和与子树中的两大链的最大值作为答案 
    		ans = 0; mx1 = -inf, mx2 = -inf;
    		for(int i : e[1]){
    			ans = max(ans, h[i] + 2);
    			if(f[i] > mx1) mx2 = mx1, mx1 = f[i];
    			else if(f[i] > mx2) mx2 = f[i];
    		}
    		if(n == 2) puts("1"); // 特判 
    		else printf("%lld\n", max(ans, mx1 + mx2 + 2));
    	}
    	return 0;
    }
    
    • 1

    信息

    ID
    9434
    时间
    2000ms
    内存
    512MiB
    难度
    6
    标签
    递交数
    0
    已通过
    0
    上传者