图论分支-Tarjan初步-点双连通分量
上一次我们讲到了边双,这次我们来看点双。
说实话来说,点双比边双稍微复杂一些;
学完边双,我们先看一道题
第一问都不用说了吧,多余的道路,明显的割边。
是不是首先想到用边双,但是我们来看一个图:
有点丑,但是凑活看吧。
它是一个边双,但是!!!!它竟然没有冲突的边!!!
此时我们就要用点双了(是不是想打死我,竟然没讲,先坑人)
先看概念
都说概念是非常重要的,但是概念似乎有点笼统,可以附图解说
点双的一大特点是它可以重复用点,而那个点就是割点,而我们的缩点操作也是用割点连接各个点双的。
那么我们来看算法,我们在Tarjan时,用栈去维护点双,之后用vector去储存。
我们直接来看上面的题,就会明白这不是点双吗?
然后寻找规律,我们会发现,每一个点双中,只要边个数大于点个数,那么这里的边都是冲突的,那么我们的Code就跃然纸上了
#include<bits/stdc++.h> #define maxn 10007 #define M 100007 using namespace std; int n,m,cent=1,t,head[maxn],low[maxn],dfn[maxn],ans1,ans2; int stackk[maxn],cnt,top,root,cut[maxn],col[maxn],vis[maxn]; int ol; vector<int >dcc[maxn]; struct node{ int next,to; }edge[M*2]; inline void add(int u,int v){ edge[++cent]=(node){head[u],v};head[u]=cent; } void Tarjan(int x,int fa){ dfn[x]=low[x]=++t; stackk[++top]=x;//入栈 if(x==root&&head[x]==0){//不联通时的孤立点 dcc[++cnt].push_back(x); return ; } for(int i=head[x];i;i=edge[i].next){ int y=edge[i].to; if(!dfn[y]){ Tarjan(y,i); low[x]=min(low[x],low[y]);//与其他的一样 if(low[y]>=dfn[x]){ if(low[y]>dfn[x]) ans1++;//记录割边个数 int z;cnt++; do{ z=stackk[top--];//从栈中取出 dcc[cnt].push_back(z);//存入 }while(z!=y);//停止条件,这个可以想一想为什么 dcc[cnt].push_back(x);//把割点也存进去 } }else if((i^1)!=fa) low[x]=min(low[x],dfn[y]); } } void clear(){ ans1=ans2=top=cnt=t=ol=0;cent=1; memset(head,0,sizeof head); memset(low,0,sizeof low); memset(dfn,0,sizeof dfn); memset(stackk,0,sizeof stackk); memset(dcc,0,sizeof dcc); memset(col,0,sizeof col); memset(edge,0,sizeof edge); } int main(){ // freopen("way.in","r",stdin); // freopen("way.out","w",stdout); while(scanf("%d%d",&n,&m)){ if(n==0&&m==0)break; clear(); int a,b; for(int i=1;i<=m;i++){ scanf("%d%d",&a,&b); add(a+1,b+1),add(b+1,a+1);//由于点从零开始,那么我们+1即可 } for(int i=1;i<=n;i++){ if(!dfn[i]) root=i,Tarjan(i,-1); } for(int i=1;i<=cnt;i++,ol=0){ for(int j=0;j<dcc[i].size();j++){ vis[dcc[i][j]]=1;//记录点双上的点 } for(int k=0;k<dcc[i].size();k++){ for(int j=head[dcc[i][k]];j;j=edge[j].next){ if(vis[edge[j].to]) ol++;//ol统计边数 } } if(ol/2>dcc[i].size()) ans2+=ol/2;//由于是双向边,ol会统计量词 memset(vis,0,sizeof(vis)); } printf("%d %d\n",ans1,ans2); } }
那么我们就应该懂了一些基本的概念,和简单的操作,那么,留一个彩蛋,我们该如何去缩点连接呢?
别困在自己的盒子里,别让自己成为世界都在议论的那只猫。