树的直径 学习笔记

树的直径

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<cstdio>
#include<iostream>
#include<cstring>
#include<queue>
using namespace std;
const int MAXX=100005;
queue<int>q;
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)<<endl;
	}else {
		change();
		dp(1);
		cout<<2*(n-1)-(l1-1)-(ans-1)<<endl;
	}
	return 0;
}

  

 

 

 

 

 

 

posted @ 2018-05-23 20:24  ART_coder  阅读(476)  评论(0编辑  收藏  举报