zoukankan      html  css  js  c++  java
  • 倍增LCA code[vs]1036商务旅行

    n个点用n-1条边连接,求两个点间的最短路

    显然可以想到用floyd预处理,但复杂度过高

    所以一些巨发明了LCA

    为什么这类最短路问题要找最近公共祖先,这是一个显然的问题,最近公共祖先说简陋了就是在这个“树”上找一个“转折点"

    LCA分为离线和在线两种,这里先写在线的方法

    在线LCA运用的思想是倍增

    何为倍增?

    在我这个渣看来,一个正整数是可以写成一个或多个n不相同的2^n的和

    那么一些时候就可以依据这一特征进行算法优化,log一下复杂度就大大降低了

    具体的代码实现大体分三部分

    首先是用邻接表实现的深搜,得到每个点的“深度”(将这个图看成树)

    需要注意的是for循环中间i的判断条件与memset初始化的head[]数组值有关

    f[edge[i].to][0]记录的值为其父节点

    1 void dfs(int p){
    2     for(int i=head[p];i;i=edge[i].next){
    3         if(!deep[edge[i].to]){
    4             deep[edge[i].to]=deep[p]+1;
    5             f[edge[i].to][0]=p;
    6             dfs(edge[i].to);
    7         }
    8     }    
    9 }

    接着是对f[][]数组预处理

    f[i][j] 表示点i向上走2^j步到达的点p 

    f[i][j-1] 表示节点i向上走2^(j-1)步到达的节点p' 

    p'再向“上”走2^j-2^(j-1)=2^(j-1)步则可到达p

    所以可以得到递推式f[i][j]=f[f[i][j-1]][j-1]

    1 void work(){
    2    for(int j=1;(1<<j)<=n;j++)
    3         for(int i=1;i<=n;i++)
    4             if(f[i][j-1]!=-1)
    5                 f[i][j]=f[f[i][j-1]][j-1];
    6 }

    最后就是LCA核心

    这里的i需要单独定义出来

    注意依据深度aa和bb,一定要从下向上走的吧

     1 int lca(int aa,int bb){
     2     int i;
     3     if(deep[aa]<deep[bb]) swap(aa,bb);
     4     for(i=0;(1<<i)<=deep[aa];i++);
     5     i--;
     6     for(int j=i;j>=0;j--) 
     7         if(deep[aa]-(1<<j)>=deep[bb])
     8             aa=f[aa][j];  
     9     if(aa==bb) return aa; 
    10     for(int j=i;j>=0;j--){
    11         if(f[aa][j]!=-1&&f[aa][j]!=f[bb][j]){
    12             aa=f[aa][j];
    13             bb=f[bb][j];
    14         }
    15     }
    16     return f[aa][0];
    17 }

    附一模板题

    1036 商务旅行

    时间限制: 1 s

    空间限制: 128000 KB

    题目等级 : 钻石 Diamond
     
    题目描述 Description

    某首都城市的商人要经常到各城镇去做生意,他们按自己的路线去做,目的是为了更好的节约时间。

    假设有N个城镇,首都编号为1,商人从首都出发,其他各城镇之间都有道路连接,任意两个城镇之间如果有直连道路,在他们之间行驶需要花费单位时间。该国公路网络发达,从首都出发能到达任意一个城镇,并且公路网络不会存在环。

    你的任务是帮助该商人计算一下他的最短旅行时间。

    输入描述 Input Description

    输入文件中的第一行有一个整数N,1<=n<=30 000,为城镇的数目。下面N-1行,每行由两个整数a 和b (1<=ab<=n; a<>b)组成,表示城镇a和城镇b有公路连接。在第N+1行为一个整数M,下面的M行,每行有该商人需要顺次经过的各城镇编号。

    输出描述 Output Description

    在输出文件中输出该商人旅行的最短时间。

    样例输入 Sample Input
    5
    1 2
    1 5
    3 5
    4 5
    4
    1
    3
    2
    5
    样例输出 Sample Output
    7
     
    注意一下f[][]数组赋初值的问题就好
     
    贴代码
     1 #include<iostream>
     2 #include<cstdio>
     3 #include<cstring>
     4 #include<algorithm>
     5 using namespace std;
     6 int n=0,x=0,y=0,cnt=0,head[30010],deep[30010],f[30010][33],m=0,a=0,b=0,ans=0;
     7 struct data{
     8     int next,to;
     9 }edge[60010];
    10 
    11 void add(int start,int end){
    12     edge[++cnt].next=head[start];
    13     edge[cnt].to=end;
    14     head[start]=cnt;
    15 }
    16 
    17 void dfs(int p){
    18     for(int i=head[p];i;i=edge[i].next){
    19         if(!deep[edge[i].to]){
    20             deep[edge[i].to]=deep[p]+1;
    21             f[edge[i].to][0]=p;
    22             dfs(edge[i].to);
    23         }
    24     }    
    25 }
    26 
    27 void work(){
    28    for(int j=1;(1<<j)<=n;j++)
    29         for(int i=1;i<=n;i++)
    30             if(f[i][j-1]!=-1)
    31                 f[i][j]=f[f[i][j-1]][j-1];
    32 }
    33 
    34 int lca(int aa,int bb){
    35     int i;
    36     if(deep[aa]<deep[bb]) swap(aa,bb);
    37     for(i=0;(1<<i)<=deep[aa];i++);
    38     i--;
    39     for(int j=i;j>=0;j--) 
    40         if(deep[aa]-(1<<j)>=deep[bb])
    41             aa=f[aa][j];  
    42     if(aa==bb) return aa; 
    43     for(int j=i;j>=0;j--){
    44         if(f[aa][j]!=-1&&f[aa][j]!=f[bb][j]){
    45             aa=f[aa][j];
    46             bb=f[bb][j];
    47         }
    48     }
    49     return f[aa][0];
    50 }
    51 
    52 int main(){
    53     scanf("%d",&n);
    54     memset(head,0,sizeof(head));
    55     memset(deep,0,sizeof(deep));
    56     memset(f,-1,sizeof(f));
    57     for(int i=1;i<n;i++){
    58         scanf("%d%d",&x,&y);
    59         add(x,y);
    60         add(y,x);
    61     }
    62     deep[1]=1;   
    63     dfs(1);
    64     work();
    65 
    66     scanf("%d",&m);
    67     scanf("%d",&a);
    68     for(int i=1;i<m;i++){
    69         scanf("%d",&b);
    70         ans+=deep[a]+deep[b]-2*deep[lca(a,b)];
    71         a=b;
    72     }
    73     printf("%d
    ",ans);
    74     return 0;
    75 }
     
  • 相关阅读:
    如何识别思科胖瘦AP
    路由器重置用户名密码
    python学习之路day09(黏包、md5和进程守护)
    python学习之路day02(容器类型和循环)
    python学习之路08(正则表达式和网络)
    python学习之路day06(模块+面向对象)
    python学习之路day05(迭代器和生成器)
    python学习之路day04(函数)
    【Linux】ssh远程时如何做到不用输入密码登入
    【中间件】GitLab安装
  • 原文地址:https://www.cnblogs.com/zwube/p/6670125.html
Copyright © 2011-2022 走看看