zoukankan      html  css  js  c++  java
  • 树的直径 学习笔记

    树的直径

    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;
    }
    

      

     

     

     

  • 相关阅读:
    正则表达式在行首添加指定内容
    linux之find命令详解
    一次安装rpcbind失败引发的思考
    配置linux实现路由功能
    chkconfig命令详解
    1225 数数字
    蛇形填数 ------- 模拟水题
    开灯问题---------简单模拟
    单源最短路径
    图的表示方式
  • 原文地址:https://www.cnblogs.com/ARTlover/p/9079147.html
Copyright © 2011-2022 走看看