hdu - 4338 - Simple Path - 割点 && 双连通
来源:互联网 发布:淘宝开店工具 编辑:程序博客网 时间:2024/04/28 21:22
//make_map
红色的对应的就是割点,黑色的是点连通分量, 当求(1,7)时候,我们分别查看1,7对应所在树中的集合,求出路径上所有的和 sum = 2 + 1 + 3 + 1 + 3 + 1 + 2 = 13 ,由于割点被重复计算,计算的次数为树边的数量,
如上图,求1,2的最短距离即为lca(1,2),而结果就是,定义树根到某点路径所有的长度为sson[i],nb[i]为集合中树节点中元素个数
#define sz 100005struct node{ int s,t,nxt;} e[sz*10];int hd[sz],cnt;void insert(int s,int t){ e[cnt].s=s; e[cnt].t=t; e[cnt].nxt=hd[s]; hd[s]=cnt++;}// tarjan割点模板,顺便求点连通分量// 如果stack overflow的话,试试在代码最前面加// #pragma comment(linker, "/STACK:102400000,102400000")int dfn[sz],low[sz],iscut[sz],sta[sz];int deep,top,bnum,root;vector <int> block[sz];void tarjan(int s,int pre){ int flag=0; dfn[s]=low[s]=++deep; sta[top++]=s; for(int i=hd[s]; i!=-1; i=e[i].nxt) { int t=e[i].t; if(t==pre && !flag)//判父边,再次出现为重边,认为连通 { flag=1; continue; } else if(dfn[t]==-1) { tarjan(t,s); low[s]=min(low[s],low[t]); if(low[t]>=dfn[s]) { if(pre == -1)root++; else iscut[s]=1; int tp; do { tp=sta[--top]; block[bnum].pb(tp); } while(tp!=t); block[bnum].pb(s); bnum++; } } else { low[s]=min(low[s],dfn[t]); } }}struct node1{ int s,t,v,nxt;} e1[sz*10];int hd1[sz],cnt1;void insert1(int s,int t){ e1[cnt1].s=s; e1[cnt1].t=t; e1[cnt1].nxt=hd1[s]; hd1[s]=cnt1++;}int lab[sz]; //用于映射int nb[sz]; //用于记录点中割点数,int dpmin[sz][20];int dpminid[sz][20];int r[sz*2],R[sz*2],lab1[sz*2],sson[sz*2],posid[sz*2];void create_Dpmin(int n){ for(int i = 1 ; i <= n ; i++ ) { dpmin[i][0] = r[i]; dpminid[i][0] = i; } for(int j = 1 ; j <= log((double)(n+1))/log(2.0) ; j++ ) for(int i = 1 ; i+(1<<j)-1 <= n ; i++ ) if(dpmin[i][j-1] < dpmin[i+(1<<(j-1))][j-1]) { dpmin[i][j] = dpmin[i][j-1] ; dpminid[i][j] = dpminid[i][j-1]; } else { dpmin[i][j] = dpmin[i+(1<<(j-1))][j-1]; dpminid[i][j] = dpminid[i+(1<<(j-1))][j-1]; }}int lca(int a,int b){ if(R[a]>R[b])return lca(b,a); a=R[a]; b=R[b]; int k = (int)(log((double)(b-a+1))/log(2.0)); return (dpmin[a][k]<dpmin[b-(1<<k)+1][k])?dpminid[a][k]:dpminid[b-(1<<k)+1][k];}void dfs(int s,int pos,int pre,int lb,int cnt){ if(R[s]==-1) { R[s]=deep; lab1[s]=lb; } posid[deep]=s; r[deep++]=pos; sson[s]=cnt+nb[s]; for(int i=hd1[s]; i!=-1; i=e1[i].nxt) { if(e1[i].t == pre)continue; dfs(e1[i].t,pos+1,s,lb,sson[s]); posid[deep]=s; r[deep++]=pos; }}void solve(int n){ //求割点,求双连通 rep(i,n)block[i].clear(); memset(iscut,0,sizeof(iscut)); memset(dfn,-1,sizeof(dfn)); deep=top=bnum=0; rep(i,n) if(dfn[i]==-1) { root=0; tarjan(i,-1); iscut[i]=(root>1); } memset(lab,-1,sizeof(lab)); int k=0; rep(i,n) if(iscut[i]) { lab[i]=k; nb[k]=1; k++; } memset(hd1,-1,sizeof(hd1)); cnt1=0; rep(i,bnum) { rep(j,block[i].size()) { int l=block[i][j]; if(iscut[l]) { insert1(lab[l],k); insert1(k,lab[l]); } else { lab[l]=k; } } nb[k]=int(block[i].size()); k++; } deep=1; memset(R,-1,sizeof(R)); rep(i,k) if(R[i]==-1) dfs(i,0,-1,i,0); create_Dpmin(deep-1); int q,s,t,u,v,fa; scanf("%d",&q); rep(i,q) { scanf("%d%d",&s,&t); if(s == t) //相同 { printf("%d\n",n-1); continue; } if(lab[s]==-1 || lab[t]==-1) // lab为-1时候,表示孤立的点,既不是割点也不是块 { printf("%d\n",n); continue; } u = lab[s]; //对应映射过去的点 v = lab[t]; if(lab1[u]!=lab1[v]) // 对块形成的树进行连通判断,不在同集合 { printf("%d\n",n); continue; } fa = lca(u,v); // 返回dfs后lca的对应在r串所在位置,r串是lca要用到的深度 fa = posid[fa];// posid对应的是r串对应的节点 int len; len = r[R[u]]+r[R[v]]-2*r[R[fa]]; //路径长度 int ans; ans = sson[u]+sson[v]-2*sson[fa]+nb[fa]; ans -= len; printf("%d\n",n-ans); }}int main(){ int n,m,s,t; int cas=1; while(~scanf("%d%d",&n,&m)) { memset(hd,-1,sizeof(hd)); cnt=0; rep(i,m) { scanf("%d%d",&s,&t); insert(s,t); insert(t,s); } printf("Case #%d:\n",cas++); solve(n); printf("\n"); }}
题目大意是说给你一个起点和终点,一个人要从起点走到终点,它不能经过一个点两次,问他不可能经过哪些点。
显然转化成能经过哪些点要好想一些,用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++交了。
#pragma comment(linker, "/STACK:102400000,102400000") #include <string.h> #include <stdio.h> #include <math.h> #include <algorithm> #define MAXN 100005 struct edge{ int u,v,n; }e1[MAXN*4],e2[MAXN*4]; int f1[MAXN],f2[MAXN*2],es1,es2; int n,m,q,tu,tv; void addedge1(int u,int v){ e1[es1].u=u,e1[es1].v=v,e1[es1].n=f1[u],f1[u]=es1++; } void addedge2(int u,int v){ e2[es2].u=u,e2[es2].v=v,e2[es2].n=f2[u],f2[u]=es2++; } //===并查集=== int p[MAXN*2]; int find(int x){return x==p[x]?x:p[x]=find(p[x]);} void merge(int x,int y){p[find(x)]=find(y);} //===DP&RMQ=== int sum[MAXN*2],tsum[MAXN*2],dis[MAXN]; int lca_f[MAXN*4],lca_b[MAXN*4],lca_p[MAXN*2],rid; int dminv[MAXN*4][20],dminid[MAXN*4][20]; void dp(int u,int f,int dd,int tot){ // printf("%d :%d %d\n",u,dd,sum[u]); dis[u]=dd,tsum[u]=tot+sum[u]; lca_f[++rid]=u,lca_b[rid]=dd,lca_p[u]=rid; for(int i=f2[u];i!=-1;i=e2[i].n){ int v=e2[i].v; if(v==f)continue; dp(v,u,dd+1,tot+sum[u]); lca_f[++rid]=u,lca_b[rid]=dd; } } void makermq(){ rid=0; dp(0,-1,0,0); for(int i=1;i<=rid;i++)dminv[i][0]=lca_b[i],dminid[i][0]=i; int maxj=(int)(log(rid+1.0)/log(2.0)); for(int j=1;j<=maxj;j++){ int maxi=rid+1-(1<<j); for(int i=1;i<=maxi;i++){ if(dminv[i][j-1]<dminv[i+(1<<(j-1))][j-1]){ dminv[i][j]=dminv[i][j-1]; dminid[i][j]=dminid[i][j-1]; }else{ dminv[i][j]=dminv[i+(1<<(j-1))][j-1]; dminid[i][j]=dminid[i+(1<<(j-1))][j-1]; } } } } int lca(int x,int y){ if(lca_p[x]>lca_p[y])std::swap(x,y); x=lca_p[x],y=lca_p[y]; int k=(int)(log(y-x+1.0)/log(2.0)); int xx=dminv[x][k]<dminv[y+1-(1<<k)][k]?dminid[x][k]:dminid[y+1-(1<<k)][k]; return lca_f[xx]; } //===Tarjan=== int dfn[MAXN],low[MAXN],cid[MAXN],stk[MAXN],col[MAXN],top,ind,cls,tmp; int cal[MAXN*2]; //为割点的条件是根节点能搜到两个分支或者low[v]>=dfn[u],找到割点并给割点标号 void dfs_cutpnt(int u,int f,int root){ dfn[u]=low[u]=++ind; int cnt=0; int flag=0; for(int i=f1[u];i!=-1;i=e1[i].n){ int v=e1[i].v; if(v==f&&!flag){flag=1;continue;} if(!dfn[v]){ cnt++; dfs_cutpnt(v,u,root); if(low[v]<low[u])low[u]=low[v]; if(u==root&&cnt>1&&cid[u]==0)cid[u]=++cls,sum[cls]=1; else if(u!=root&&low[v]>=dfn[u]&&cid[u]==0)cid[u]=++cls,sum[cls]=1; }else if(dfn[v]<low[u])low[u]=dfn[v]; } } //找双联通分量并给双联通分量标号,当这个双联通分量中包含某个割点时,连一条边 void dfs_tarjan(int u,int f){ low[u]=dfn[u]=++ind; stk[++top]=u; int flag=0; for(int i=f1[u];i!=-1;i=e1[i].n){ int v=e1[i].v; if(v==f&&!flag){flag=1;continue;} if(!dfn[v]){ dfs_tarjan(v,u); if(low[v]<low[u])low[u]=low[v]; if(low[v]>=dfn[u]){ sum[++cls]=1,col[u]=cls; do{ tmp=stk[top--],col[tmp]=cls,++sum[cls]; if(cid[tmp]){addedge2(cid[tmp],cls);addedge2(cls,cid[tmp]);merge(cid[tmp],cls);} }while(tmp!=v); if(cid[u]){addedge2(cid[u],cls);addedge2(cls,cid[u]);merge(cid[u],cls);} } }else if(dfn[v]<low[u])low[u]=dfn[v]; } } int size; void makegraph(){ //找割点 memset(dfn,0,sizeof dfn); memset(low,0,sizeof low); memset(cid,0,sizeof cid); cls=ind=0; //找双联通分量并建图 for(int i=0;i<n;i++)dfs_cutpnt(i,-1,i); memset(dfn,0,sizeof dfn); memset(low,0,sizeof low); memset(col,0,sizeof col); top=ind=0; for(int i=0;i<n;i++)dfs_tarjan(i,-1); //将森林补成树,便于dp以及查询 memset(cal,0,sizeof cal); for(int i=1;i<=cls;i++){ if(cal[find(i)]==0){ cal[find(i)]=1; addedge2(0,i); } } } int main(){ //freopen("test.in","r",stdin); int cas=1; while(scanf("%d%d",&n,&m)!=EOF){ memset(f1,-1,sizeof f1); memset(f2,-1,sizeof f2); for(int i=0;i<=2*n;i++)p[i]=i; es1=es2=0; for(int i=0;i<m;i++){ scanf("%d%d",&tu,&tv); addedge1(tu,tv); addedge1(tv,tu); } //转化成双联通与割点相邻的图 makegraph(); //lca转化成rmq makermq(); printf("Case #%d:\n",cas++); scanf("%d",&q); while(q--){ scanf("%d%d",&tu,&tv); //起点和终点重合 if(tu==tv)printf("%d\n",n-1); else{ //如果是割点的话就一定要用割点对应的点,因为割点会被染成不同的颜色! tu=cid[tu]?cid[tu]:col[tu]; tv=cid[tv]?cid[tv]:col[tv]; //孤立点或者不在同一个联通块中 if(tu==0||tv==0||find(tu)!=find(tv)){ printf("%d\n",n); }else{ int fa=lca(tu,tv); int ans=tsum[tu]+tsum[tv]-2*tsum[fa]+sum[fa]; ans-=(dis[tu]+dis[tv]-2*dis[fa]); printf("%d\n",n-ans); } } } printf("\n"); } return 0; }
- hdu - 4338 - Simple Path - 割点 && 双连通
- HDU 4338 Simple Path 点双连通+lca
- hdu 3686 点双连通 按<割点>缩点 + 倍增lca
- hdu 3394 Railway【点双连通分量、桥、割点】
- 【图论】割点、桥、双连通
- hdu 4338 Simple Path
- 无向图的割点,割边,点双连通,边双连通模板
- hdu 3749 点双连通
- hdu 3394(点双连通)
- hdu 5739(点双连通)
- HDU 5739(点双连通)
- 割点、割边、双连通分支
- 割点、割边、双连通分支
- 割点、桥、点双连通、边双连通、强连通 题目
- 无向图的桥、割点、边双连通
- HDU-4587 TWO NODES(割点变形或者求点双连通分量)
- HDU 5739 (点双连通 树DP)
- 割点,桥,双连通分支
- Hibernate框架ORM的实现原理
- 分手快乐,祝你快乐,你找不到比我更好的。
- gdb调试正在运行的进程
- copy_to_user、copy_from_user 分析
- Accessing Domain Fixed Values
- hdu - 4338 - Simple Path - 割点 && 双连通
- Error is: '[Deployer:149164]The domain edit lock is owned by another session in exclusive mode - he
- Android学习 - android中发送短信的方法
- mmap 文件内存映射机制(zz)
- VC++深入详解(9):图形的保存和重绘
- 防止CListCtrl闪烁的几种方法
- linux 下视频设备设置的几个参数 v4l video4linux v4l2 ioctl
- H3C inode Linux Mac OS 上网认证客户端
- 数据库连接池[java+oracle11+tomcat7.x]