Codeforces 835F Round #427 Div2F :树形DP

xiaoxiao2021-02-28  73

本题同NOI2013快餐店。

题意:给出一张无向图,其中有n个点,n条边,(n<=2e5)没有自环和重边。求去掉一条边形成一棵树之后,树的直径的最小值。

题解:首先这个图是一个带环外向树的图。就是一个环,然后以某些环上的点为根可以延伸出一棵树。那么生成树的个数等于环上的边数,题目意思就是快速的求出这些生成树的直径。那么先考虑如果某个生成树的直径在外向树上面,那么可以很容易的DP求出外向树直径。因为外向树上面的边是不能够删除的,因此外向树完整的被包括在任何一个生成树中,所以答案至少是外向树上直径最大值。

继续考虑第二类直径:直径的一部分在还上。那么这个直径可以分成三部分:第一段是某个外向树的高度(外向树最深的点到根的距离),第二段是环上的一段,第三段也是一个外向树的高度。那么我们显然可以通过枚举删掉的边,然后求这样三段的一个最大值得到该树的直径(伪直径,因为去掉了直径完全在外向树上的情况) 然后取最小来得到正确答案,但是复杂度太高。

问题回到环上,既然是带环的问题,通常的思路是,去掉任意一条边,把他变成一个链再做考虑。那么假设我们去掉 x 这个环上的边不做考虑,那么我们得到了一个链,同时这个链每个点有一个权重=外向树的最深deep。那么根据我们上边说的这个伪直径的形状:一个权重+一段弧+一个权重。

假如我们去掉的边就是x,那么这棵树上的伪直径都是完整的呈现在链上:通过一个DP可以求得他们的最大值,扫一遍就行了,因为现在已经破开了环,变成了线性的问题,略去不讲(提示:伪直径 = max(弧长+权重)+ max(权重-弧长)=max(权重+弧长-弧长+权重)=max(权重+两个权重之间的弧长+权重))。

假如去掉的边不是x,讨论伪直径的形态:第一、不包括x,这样的伪直径在链上也是以完整形态表现出来,其实在前边已经算过了,不需要再考虑;第二、包括x:那么这个伪直径分为三部分:一段弧+一个权重、x、一段弧长+一个权重,而且这两个(弧长+权重)分布在x的左右两边,也就是顺时针、逆时针两个方向上。那么我们可以在 *链上* 按照和上边相同的思路做两个DP,处理出从x开始,分别顺时针、逆时针的(弧长+权重)的一组dp值。然后再扫一遍,线性枚举一下答案。同时,其实有第三种:弧长+权重,这个其实可以看作 0权重+弧长+权重,和第二类是一样的。

代码说明:best[] 和 best1[] 是计算 两个方向上的 伪直径的最大值的dp数组。good[] 和good1[]是计算 两个方向上的 (弧长+权重)的dp数组。lastLength=断开环的那条边长。

circleNode[]按顺序记录了环上的点。circleLength[]按顺序记录了环上每条边,和circleNode是一一对应。

Code:

#include<bits/stdc++.h> #define INF 0x3f3f3f3f #define LL long long using namespace std; const int MAX = 200050; struct Edge{ int des;LL length; Edge(int des_,int length_):des(des_),length(length_){} }; vector<Edge>E[MAX]; int n; bool vis[MAX]; bool inCircle[MAX]; int circleNode[MAX]; int circleNum; LL circleLength[MAX]; int father[MAX]; LL father_length[MAX]; LL f[MAX]; void dfs_find(int x,int fa){ vis[x] = true; for (Edge temp:E[x]){ int v = temp.des; int length = temp.length; if (v==fa) continue; if (vis[v]){ if (inCircle[v]){ continue; } int u = x; while (u!=v){ inCircle[u] =true; circleNode[++circleNum] = u; circleLength[circleNum] = father_length[u]; u = father[u]; } inCircle[v] = true; circleNode[++circleNum] = v; circleLength[circleNum] = length; continue; } father[v] = x; father_length[v] = length; dfs_find(v,x); } } LL chain=-1; void dfs_dp(int x,int fa){ for (Edge temp :E[x]){ int v = temp.des; LL length = temp.length; if (v==fa||inCircle[v]){ continue; } dfs_dp(v,x); chain = max(chain,f[x]+f[v]+length); f[x] = max(f[x],f[v]+length); } } LL good[MAX],good1[MAX],best[MAX],best1[MAX]; int main(){ scanf("%d",&n); for (int i=0;i<n;i++){ int u,v,l; scanf("%d%d%d",&u,&v,&l); E[u].push_back(Edge(v,l)); E[v].push_back(Edge(u,l)); } dfs_find(1,0); // cout<<"OK"<<endl; for (int i=1;i<=circleNum;i++){ dfs_dp(circleNode[i],0); } LL sum = 0; LL support =-INF; // cout<<"YES"<<endl; for (int i=1;i<=circleNum;i++){ sum+=circleLength[i-1]; good[i] = max(good[i-1],sum+f[circleNode[i]]); best[i] = max(best[i-1],sum+f[circleNode[i]]+support); support = max(support,f[circleNode[i]]-sum); } sum = -circleLength[circleNum]; support = -INF; for (int i=circleNum;i>=1;i--){ // cout<<i<<endl; sum+=circleLength[i]; good1[i] = max(good1[i+1],sum+f[circleNode[i]]); best1[i] = max(best1[i+1],sum+f[circleNode[i]]+support); support = max(support,f[circleNode[i]]-sum); } // cout<<"YES"<<endl; LL ans = best[circleNum]; int lastLength = circleLength[circleNum]; /* printf("last:%d\n",lastLength); for (int i=1;i<=circleNum;i++){ printf("good[%d]=%d ",i,good[i]); printf("good1[%d]=%d ",i,good1[i]); printf("best[%d]=%d ",i,best[i]); printf("best1[%d]=%d\n",i,best1[i]); } */ for (int i=1;i<circleNum;i++){ LL temp = good[i]+good1[i+1]+lastLength; temp = max(max(best[i],best1[i+1]),temp); ans = min(ans,temp); } ans = max(chain,ans); printf("%I64d\n",ans); return 0; }

转载请注明原文地址: https://www.6miu.com/read-54897.html

最新回复(0)