浅谈树的直径
定义:
树的直径指树上最长链(最远点对)
求解:
树的直径存在两种求解方式均为O(n)复杂度,其各有优劣
1.贪心法
任取一点作为起点,找到树上距离该点的最远点,记作st,再以st为起点,找到树上距离st最远的点,记作ed,st至ed即为树的直径。
(找最远点操作DFS和BFS均可)
优点:起点与终点方便获得。
缺点:负边权就GG。
inline void dfs(int now,int fa,int deep)
{
if(deep>res)
{
res=deep;
ed=now;
}
f[now]=fa;
dep[now]=deep;
for(int i=head[now];i;i=a[i].nxt)
{
int t=a[i].to;
if(t==fa) continue;
dfs(t,now,deep+a[i].val);
}
}
dfs(1,0,0);
res=0;
st=ed;
dfs(st,0,0);
2.树型DP
任取一点作为起点,记录树上每一点向下的最远距离和非严格次远距离,直径长度即为每一点二者之和的最大值。
优点:能处理负边权。
缺点:起点终点难以记录。
inline void dfs(int now,int fa)
{
for(int i=head[now];i;i=a[i].nxt)
{
int t=a[i].to;
if(t==fa) continue;
dfs(t,now);
res=max(res,dp[now]+dp[t]+a[i].val);
dp[now]=max(dp[now],dp[t]+a[i].val);
}
}
性质:
1.直径两端点一定是叶子节点。
2.距任意点最远点一定是直径的端点,据所有点最大值最小的点一定是直径的中点。
3.两棵树相连,新直径的两端点一定是原四个端点中的两个
4.两棵树相连,新直径长度最小为max(max(直径1,直径2),半径1+半径2+新边长度 ) (设k为直径中最接近中点的节点,半径=max(tot-d[k],d[k]))
5.一棵树上接一个叶子结点,直径最多改变一个端点
6.若一棵树存在多条直径,多条直径交于一点,且交点是直径的严格中点(中点可能在某条边内)
例题:
前言:树的直径姿势点主要考察各种性质的应用,灵活运用各种性质即可切题。
洛谷P3629APIO 2010巡逻
题目概述:有一棵树,要求我们在树上加上1~2两条边,使遍历每一条路时经过的路径最短,关于路径,有以下几个要求
1.新建的边必须正好经历一次
2.新边可以是自环
3.遍历后回到起点
分析:既然要回到起点,在无环情况下,每条边必然要走两遍,路径长度为2*n-2;
对于k=1的情况,一个贪心的想法必然是让树上最长边形成一个环,所以求一遍树的直径即可,设树的直径长度为res,路径长度变为2*n-2-res+1;
而对于k=2的情况,在加完第一条边后,必然要找第二条最长的边
然而在找第二条最长边的时候我们发现:若第二条边与第一条边有重叠部分,那么重叠部分就要再走一次,相当于减少了第一次的贡献
同时,由于建立新边是为了防止一条边被第二次遍历,第一条最长边选中的部分本身遍不用第二次遍历,所以该边对于最长边的贡献本就应是零
综合本来的贡献和减少的第一次贡献,改变对于第二次求最长边的贡献应该是边权的负值XD,此时我们应该将第一条最长边的边权全部取反,再找一条最长边。
在取反后,树上遍有了负边权,我们需要采取树型DP求第二条最长边,而且由于需要取反第一条最长边,我们需要第一条最长边的路径,第一条适合用dfs求最长边
总结:该题目综合考察了DFS求树的直径和树型DP求树的直径的优点。
洛谷P2491 SDOI2011消防
题目概述:在一颗树上选取一段区间,长度小于m,使得所有节点到该区间的最大值最小。
分析:由性质2:据任意点最大值最小的点一定是直径的中点 可知:中点必然入选。
再考虑将中点扩展为区间:据任意点最远点一定是直径的中点可知,若扩展为区间,在直径上扩展一定是最优的
可反证:若存在另一点到直径上一点距离大于直径端点到直径上一点距离,那么将会产生新的直径。
现在问题转化为在直径上选取一段区间长度小于m,使得任意一点到这段区间的最大值最小;
我们可以预处理出其他节点到达直径的最远距离,一次为l,以直径长度为r,二分答案(判断根据性质2只需判断该区间到直径端点距离是否小于mid)
近一步我们可以双指针法直接求答案
注意一个特判:若m>=直径长度,直接输出l。
总结:重点考察了性质2和相关应用。
洛谷P3761 TJOI2017城市
题目概述:在树上断开一条边连到别处,使得新树的直径最小,求直径最小值。
分析:为了改变新树直径,断开的边必然在原树直径上,找到一条直径,枚举断开哪一条边。(若有多条直径也没法管)
根据性质4:新树直径最小值为max(max(子树1直径,子树2直径),子树1半径+子树2半径+断开边长度),在所有新树直径中取最小值即可。
总结:考察性质4运用。
洛谷P3304 SDOI2013直径
题目概述:给定一棵树,求所有直径都共同经过的边的数量。
分析:根据性质6:所有直径必然交于中点,我们只要分析中点在边内和在节点上的情况。
情况1:中点在边内:ans=1,对于该边的两个节点做一次以下操作:
处理出每个点能够到达的最大深度,记作d[now],若存在0个或1个以上的子节点满足d[t]+边权==d[now]则该节点向下不存在直径必经边。
若仅存在1个子节点d[t]+边权==d[now],++ans,再对该子节点做一个相同操作。
情况2:中点在点上:处理出每个点能够到达的最大深度,扔进堆里,取前三大的点比较,设为a,b,c。
若不存在c或a=b>c,ans=2,a,b,做一次上述操作。
若a=b=c,则不存在必经边。
该题目略难以理解(至少弱鸡作者这么认为的QWQ),所以将代码放上,但是由于各大OJ该题数据普遍过水,不存在中点在节点上的数据,作者并不能保证代码的正确性,若有大神发现不妥之处欢迎指正
#includeusing namespace std; #define int long long typedef pair<int,int> p; priority_queue =read(),y=read(),z=read(); add(x,y,z); add(y,x,z); } dfs(1,0,0); res=0; dfs(st,0,0); printf("%lld\n",res); while(2*dep[f[st]]>=res) st=f[st]; if(2*dep[st]==res) { dfs(st,0,0); for(int i=head[st];i;i=a[i].nxt) { int t=a[i].to ; q.push(p(d[t]+a[i].val,t)); } p a=q.top(); q.pop(); p b=q.top(); q.pop(); if(!q.empty()) { ans=2; work(a.second,st); work(b.second,st); } else { p c=q.top(); if(c.first==a.first) { ans=0; } else { ans=2; work(a.second,st); work(b.second,st); } } } else { ans=1; work(st,f[st]); work(f[st],st); } printf("%lld\n",ans); return 0; }q; inline int read() { int x=0,f=1; char ch; for(ch=getchar();(ch<'0'||ch>'9')&&ch!='-';ch=getchar()); if(ch=='-') f=0,ch=getchar(); while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();} return f?x:-x; } int n,st,res,tot,now,ans; int f[200010],dep[200010],d[200010]; int head[200010],cnt; struct point { int nxt,to,val; }a[500010]; inline void add(int x,int y,int z) { a[++cnt].nxt=head[x]; a[cnt].to=y; a[cnt].val=z; head[x]=cnt; } inline void dfs(int now,int fa,int deep) { f[now]=fa; dep[now]=deep; d[now]=0; if(deep>res) { res=deep; st=now; } for(int i=head[now];i;i=a[i].nxt) { int t=a[i].to; if(t==fa) continue; dfs(t,now,deep+a[i].val); d[now]=max(d[now],d[t]+a[i].val); } } inline void work(int x,int y) { dfs(x,y,0); while("miao") { int t=0,sum=0; for(int i=head[x];i;i=a[i].nxt) { if(a[i].to==y) continue; if(d[x]==d[a[i].to]+a[i].val) { t=a[i].to; ++sum; } } if(sum^1) break; y=x; x=t; ++ans; } } signed main() { n=read(); for(int x,y,z,i=1;i
i) { x
总结:考察性质6以及直径中点相关知识。