HDU 4338 Simple Path [双联通分量+RMQ(LCA)]

  题目大意是说给你一个起点和终点,一个人要从起点走到终点,它不能经过一个点两次,问他不可能经过哪些点。

  显然转化成能经过哪些点要好想一些,用N减去能经过的点就可以得到答案。下面的讨论都是基于求他可能经过的点有多少个。

  很容易想到用双联通分量,但是建图确实比较麻烦。如下图,一个人想要从1走到3,那它可能在的点就是1,2,3。因为2是一个割点,它如果从2走到了4,想要到达3就必须再经过2,所以可以用割点和双联通建图。

  用割点和双联通可以建成双联通与割点相邻的图,若右图所示。可以证明这是一棵树,因为如果存在环,这个环必然可以缩成一个点,树中任意两点有且仅有一条路径,于是只要统计这条路径上有一共多少个点即可。但是有一个问题就是割点会被重复统计,每一个割点会被它左边以及右边的双连通分量各多统计一次,因此只要将点数减去路径上的边数即可,例如求1-3,答案就是2+1+2-2=3。

 

  

  

        

  快速计算路径上的点数总和要用到LCA,先DP处理出每个节点到根节点的距离dis[u],以及到根节点的节点总数Tsum[u],每个节点的包含的节点数记为sum[u],则这条路径上的点数为ans=(Tsum[u]+Tsum[v]-2*Tsum[lca(u,v)]+sum[lca(u,v)])-(dis[u]+dis[v]-2*dis[lca(u,v)])。LCA可以转化成RMQ以便在线查询,每次查询复杂度logN。

  还要注意几个小问题,在建树的时候用并查集进行合并,这样就可以快速判断两个点是否在同一个连通分量中;另外在可以虚拟一个父亲节点,到所有的连通块中各连一条边,这样可以使dp方便很多。查询时,对点进行映射,如果这个点是割点,一定要将其映射到割点所对应的节点,因为割点在tarjan中是会被染成不同的颜色的。

  有几个小trick,起点与终点可能不联通,也可能在同一个点,需要特判。

  比较坑的是DP会爆栈,但是不用DP将LCA转RMQ会比较麻烦,只能手动扩栈(加上第一行),然后用C++交了。

  

  1 #pragma comment(linker, "/STACK:102400000,102400000")
  2 #include <string.h>
  3 #include <stdio.h>
  4 #include <math.h>
  5 #include <algorithm>
  6 #define MAXN 100005
  7 struct edge{
  8     int u,v,n;
  9 }e1[MAXN*4],e2[MAXN*4];
 10 int f1[MAXN],f2[MAXN*2],es1,es2;
 11 int n,m,q,tu,tv;
 12 void addedge1(int u,int v){
 13     e1[es1].u=u,e1[es1].v=v,e1[es1].n=f1[u],f1[u]=es1++;
 14 }
 15 void addedge2(int u,int v){
 16     e2[es2].u=u,e2[es2].v=v,e2[es2].n=f2[u],f2[u]=es2++;
 17 }
 18 //===并查集===
 19 int p[MAXN*2];
 20 int find(int x){return x==p[x]?x:p[x]=find(p[x]);}
 21 void merge(int x,int y){p[find(x)]=find(y);}
 22 //===DP&RMQ===
 23 int sum[MAXN*2],tsum[MAXN*2],dis[MAXN];
 24 int lca_f[MAXN*4],lca_b[MAXN*4],lca_p[MAXN*2],rid;
 25 int dminv[MAXN*4][20],dminid[MAXN*4][20];
 26 void dp(int u,int f,int dd,int tot){
 27    // printf("%d :%d %d\n",u,dd,sum[u]);
 28     dis[u]=dd,tsum[u]=tot+sum[u];
 29     lca_f[++rid]=u,lca_b[rid]=dd,lca_p[u]=rid;
 30     for(int i=f2[u];i!=-1;i=e2[i].n){
 31         int v=e2[i].v;
 32         if(v==f)continue;
 33         dp(v,u,dd+1,tot+sum[u]);
 34         lca_f[++rid]=u,lca_b[rid]=dd;
 35     }
 36 }
 37 void makermq(){
 38     rid=0;
 39     dp(0,-1,0,0);
 40     for(int i=1;i<=rid;i++)dminv[i][0]=lca_b[i],dminid[i][0]=i;
 41     int maxj=(int)(log(rid+1.0)/log(2.0));
 42     for(int j=1;j<=maxj;j++){
 43         int maxi=rid+1-(1<<j);
 44         for(int i=1;i<=maxi;i++){
 45             if(dminv[i][j-1]<dminv[i+(1<<(j-1))][j-1]){
 46                 dminv[i][j]=dminv[i][j-1];
 47                 dminid[i][j]=dminid[i][j-1];
 48             }else{
 49                 dminv[i][j]=dminv[i+(1<<(j-1))][j-1];
 50                 dminid[i][j]=dminid[i+(1<<(j-1))][j-1];
 51             }
 52         }
 53     }
 54 }
 55 int lca(int x,int y){
 56     if(lca_p[x]>lca_p[y])std::swap(x,y);
 57     x=lca_p[x],y=lca_p[y];
 58     int k=(int)(log(y-x+1.0)/log(2.0));
 59     int xx=dminv[x][k]<dminv[y+1-(1<<k)][k]?dminid[x][k]:dminid[y+1-(1<<k)][k];
 60     return lca_f[xx];
 61 }
 62 //===Tarjan===
 63 int dfn[MAXN],low[MAXN],cid[MAXN],stk[MAXN],col[MAXN],top,ind,cls,tmp;
 64 int cal[MAXN*2];
 65 //为割点的条件是根节点能搜到两个分支或者low[v]>=dfn[u],找到割点并给割点标号
 66 void dfs_cutpnt(int u,int f,int root){
 67     dfn[u]=low[u]=++ind;
 68     int cnt=0;
 69     int flag=0;
 70     for(int i=f1[u];i!=-1;i=e1[i].n){
 71         int v=e1[i].v;
 72         if(v==f&&!flag){flag=1;continue;}
 73         if(!dfn[v]){
 74             cnt++;
 75             dfs_cutpnt(v,u,root);
 76             if(low[v]<low[u])low[u]=low[v];
 77             if(u==root&&cnt>1&&cid[u]==0)cid[u]=++cls,sum[cls]=1;
 78             else if(u!=root&&low[v]>=dfn[u]&&cid[u]==0)cid[u]=++cls,sum[cls]=1;
 79         }else if(dfn[v]<low[u])low[u]=dfn[v];
 80     }
 81 }
 82 //找双联通分量并给双联通分量标号,当这个双联通分量中包含某个割点时,连一条边
 83 void dfs_tarjan(int u,int f){
 84     low[u]=dfn[u]=++ind;
 85     stk[++top]=u;
 86     int flag=0;
 87     for(int i=f1[u];i!=-1;i=e1[i].n){
 88         int v=e1[i].v;
 89         if(v==f&&!flag){flag=1;continue;}
 90         if(!dfn[v]){
 91             dfs_tarjan(v,u);
 92             if(low[v]<low[u])low[u]=low[v];
 93             if(low[v]>=dfn[u]){
 94                 sum[++cls]=1,col[u]=cls;
 95                 do{
 96                     tmp=stk[top--],col[tmp]=cls,++sum[cls];
 97                     if(cid[tmp]){addedge2(cid[tmp],cls);addedge2(cls,cid[tmp]);merge(cid[tmp],cls);}
 98                 }while(tmp!=v);
 99                 if(cid[u]){addedge2(cid[u],cls);addedge2(cls,cid[u]);merge(cid[u],cls);}
100             }
101         }else if(dfn[v]<low[u])low[u]=dfn[v];
102     }
103 }
104 int size;
105 void makegraph(){
106     //找割点
107     memset(dfn,0,sizeof dfn);
108     memset(low,0,sizeof low);
109     memset(cid,0,sizeof cid);
110     cls=ind=0;
111     //找双联通分量并建图
112     for(int i=0;i<n;i++)dfs_cutpnt(i,-1,i);
113     memset(dfn,0,sizeof dfn);
114     memset(low,0,sizeof low);
115     memset(col,0,sizeof col);
116     top=ind=0;
117     for(int i=0;i<n;i++)dfs_tarjan(i,-1);
118     //将森林补成树,便于dp以及查询
119     memset(cal,0,sizeof cal);
120     for(int i=1;i<=cls;i++){
121         if(cal[find(i)]==0){
122             cal[find(i)]=1;
123             addedge2(0,i);
124         }
125     }
126 }
127 int main(){
128     //freopen("test.in","r",stdin);
129     int cas=1;
130     while(scanf("%d%d",&n,&m)!=EOF){
131         memset(f1,-1,sizeof f1);
132         memset(f2,-1,sizeof f2);
133         for(int i=0;i<=2*n;i++)p[i]=i;
134         es1=es2=0;
135 
136         for(int i=0;i<m;i++){
137             scanf("%d%d",&tu,&tv);
138             addedge1(tu,tv);
139             addedge1(tv,tu);
140         }
141 
142         //转化成双联通与割点相邻的图
143         makegraph();
144         //lca转化成rmq
145         makermq();
146 
147         printf("Case #%d:\n",cas++);
148         scanf("%d",&q);
149         while(q--){
150             scanf("%d%d",&tu,&tv);
151             //起点和终点重合
152             if(tu==tv)printf("%d\n",n-1);
153             else{
154                 //如果是割点的话就一定要用割点对应的点,因为割点会被染成不同的颜色!
155                 tu=cid[tu]?cid[tu]:col[tu];
156                 tv=cid[tv]?cid[tv]:col[tv];
157                 //孤立点或者不在同一个联通块中
158                 if(tu==0||tv==0||find(tu)!=find(tv)){
159                     printf("%d\n",n);
160                 }else{
161                     int fa=lca(tu,tv);
162                     int ans=tsum[tu]+tsum[tv]-2*tsum[fa]+sum[fa];
163                     ans-=(dis[tu]+dis[tv]-2*dis[fa]);
164                     printf("%d\n",n-ans);
165                 }
166             }
167         }
168         printf("\n");
169     }
170     return 0;
171 }

转载于:https://www.cnblogs.com/swm8023/archive/2012/08/29/2662580.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值