树的直径
define:树上最长链
solution:
1.树形dp
状态:d[x],表示x到达以x为根子树的最远距离
转移: ans=max(ans,d[x]+d[y]+edge[i]);d[x]=max(d[x],d[y]+edge[i])
注意 ans的更新 :因为转移顺序是底到根更新,走到yi时,d[x]已经储存了d[yj]的信息,并且,没有储存d[yi],则可用d[x]+d[yi]+e[x,yi]更新;
复杂度O(n)
优势:好写
缺点:难以记录路径
void dp(int x){
v[x]=1;
for(int i=head[x];i;i=ver[i]){
int y=ver[i];
if(v[y])continue;
dp(y);
ans=max(ans,d[x]+d[y]+edge[i]);
d[x]=max(d[x],d[y]+edge[i]);
}
}
2.两边bfs/dfs
过程:first:任选一点x为根,bfs/dfs,更新到x的最远路径,记录到达点p
second:以点p为根,bfs/dfs,更新到p的最远路径,记录到达点q,则从p到q即为树的直径
证明:可以考虑反证法
假设此树的最长路径是从s到t,我们选择的点为u。反证法:假设搜到的点是v。
1、v在这条最长路径上:dis[u,v]>dis[u,v]+dis[v,s],显然矛盾。
2、v不在这条最长路径上:设点p在e(s,t)上:
dis[u,v]>dis[u,p]+dis[p,t];
dis[s,v]=dis[s,p]+dis[p,u]+dis[u,v];
dis[p,u]+dis[u,v]>dis[p,t];
dis[s,v]>dis[s,p]+dis[p,t]=dis[s,t]
dis[s,v]>dis[s,t],矛盾。
复杂度O(n)
优势:方便记录路径(记录路径,可以在第一遍bfs、dfs中记录前继来记录,而dp却显不出这样的的优势)
缺点:代码较长,难写
int bfs(int s){
memset(d,0x3f,sizeof(d));
d[s]=0;
pre[s]=0;
q.push(s);
while(!q.empty()){
int x=q.front();
q.pop();
for(int i=head[x];i;i=nxt[i]){
int y=ver[i];
if(d[y]==0x3f3f3f3f){
d[y]=d[x]+edge[i];
pre[y]=i;
q.push(y);
}
}
}
int y=1;
for(int x=1;x<=n;x++){
if(d[x]>d[y])y=x;
}
return y;
}
主程序中:
int p;
p=bfs(1);//任意一点
p=bfs(p);
ans=d[p];
例题:
APIO 2010(巡逻)
题面:给定一棵树:要求加k条边(边权为1),使得,从1开始走并回到1,当经过了所有点,走的路径最短;(1<=k<=2)
本实际上 加上一些边使部分边(L长)连成环,则可以少走(L-1)路径长
so:找到树上最长链即树的直径,可以使路径最短
当k=1,可以直接输出2(n-1)-(L-1)
但是当k=2,考虑到可能有重合,我们可以在第一遍找直径时,将此直径所有的点标记为-1,可以避免环重合
因此:因为第一遍我们需要记录直径路径,故使用两边bfs,将直径上的路径处理为-1;之后即可以树形dp一遍
代码如下
#include
#include
#include
#include
using namespace std;
const int MAXX=100005;
queueq;
int head[MAXX],ver[MAXX*2],edge[MAXX*2],nxt[MAXX*2];
int d[MAXX],pre[MAXX],f[MAXX];
bool v[MAXX];
int n,k,p,tot=1,ans;
void add(int x,int y,int z){
ver[++tot]=y;
nxt[tot]=head[x];
head[x]=tot;
edge[tot]=z;
}
int bfs(int s){
memset(d,0x3f,sizeof(d));
d[s]=0;
pre[s]=0;
q.push(s);
while(!q.empty()){
int x=q.front();
q.pop();
for(int i=head[x];i;i=nxt[i]){
int y=ver[i];
if(d[y]==0x3f3f3f3f){
pre[y]=i;
d[y]=d[x]+edge[i];
q.push(y);
}
}
}
int x,y;
for( x=y=1;x<=n;x++){
if(d[x]>d[y])y=x;
}//找到端点
return y;
}
void dp(int x){
v[x]=1;
for(int i=head[x];i;i=nxt[i]){
int y=ver[i];
if(v[y])continue;
dp(y);
ans=max(ans,f[x]+f[y]+edge[i]);
f[x]=max(f[x],f[y]+edge[i]);
}
}
void change(){
for(;pre[p];p=ver[pre[p]^1])edge[pre[p]]=edge[pre[p]^1]=-1;
//取反操作,ver[pre[p]^1],实际上是反边的终点,也就是p的前继
//因为tot=1;++tot,边从2开始记录,2^1=3,即2,3互为反边
}
int main(){
cin>>n>>k;
for(int i=1;i<=n-1;i++){
int x,y;
cin>>x>>y;
add(x,y,1);
add(y,x,1);
}
p=bfs(1);
p=bfs(p);
int l1=d[p];
if(k==1){
cout<<2*(n-1)-(l1-1)<