1.边连通分量
边连通分量(e-DCC): 就是删除无向图的一条边,假如该图不连通了,则这条边叫做桥,边连通分量就是极大的不含桥的连通分量
1.弄连个时间戳dfn表示第一次遍历到这个点的时间戳,low是他的子树中的最小的时间戳
2.假如是桥,则dfn[x]<low[y],则说明x->y这条边就是桥
3.用stack存边连通分量,因为假如low[x]==dfn[x],说明找到了这个点的祖宗节点,则入栈的就是他的子树,也就是边连通分量
1.冗余路径
题目大意就是:给定一个无向连通图,问最少加多少条边,使得该图变成一个边双连通分量
先做一遍边的双连通分量,然后缩点建图,变成一棵树,答案就叶节点(也即度数为1的点)的一半向上取整,则ans=[cnt+1]/2
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int N=5010,M=20010; int n,m; int h[N],e[M],ne[M],idx; int dfn[N],low[N],timestamp; int stk[N],top; int id[N],dcc_cnt; bool is_bridge[M]; int d[N]; void add(int a,int b) { e[idx]=b,ne[idx]=h[a],h[a]=idx++; } void tarjan(int u, int from)//u是当前节点,from是从那个节点更新过来的 { dfn[u]=low[u]=++timestamp;//第一次遍历让她们都等于时间戳 stk[++top]=u;//把这个点入栈 for(int i=h[u];~i;i=ne[i]) { int j=e[i]; if(!dfn[j])//假如没有遍历过 { tarjan(j,i);//遍历一遍 low[u]=min(low[u],low[j]); if(dfn[u]<low[j])//假如是桥 is_bridge[i]=is_bridge[i^1]=true;//正向边和反向边都是标记为桥,反向边就是i^1 } else if(i!=(from^1)) low[u]=min(low[u],dfn[j]);//假如正向边不等于转移过来的边,也即不是父节点,则更新 } if(dfn[u]==low[u])//假如相等了说明找到了一个连通块 { ++dcc_cnt;//连通块++ int y; do { y=stk[top--];//栈里的元素就是他的连通块元素 id[y]=dcc_cnt;//标记这个点是属于这个连通块的 }while(y!=u); } } int main() { cin>>n>>m; memset(h,-1,sizeof h); while(m--) { int a,b; cin>>a>>b; add(a,b),add(b,a); } tarjan(1,-1);//搜索一下所有的边 for(int i=0;i<idx;i++)//枚举一下所有的边 if(is_bridge[i])//假如这条边是桥 d[id[e[i]]]++;//则则这条边的上一个边连通分量度数++ int cnt=0; for(int i=1;i<=dcc_cnt;i++)//缩点后的图 if(d[i]==1) cnt++;//算一遍度数为1的边 cout<<(cnt+1)/2<<endl; return 0; }
2.点连通分量
点连通分量(v-DCC):就是删除无向图的一个点,假如该图不连通了,则这个点叫做割点,点连通分量就是极大的不含割点的连通分量
1.弄连个时间戳dfn表示第一次遍历到这个点的时间戳,low是他的子树中的最小的时间戳
2.求割点:当low[y]>=dfn[x],
(1)假如x不是根节点,那么x是割点
(2)假如是根节点,至少有两个字节点yi使得low[yi]>=dfn[x],则x是割点
3.用stack存边连通分量,因为假如low[x]==dfn[x],说明找到了这个点的祖宗节点,则入栈的就是他的子树,也就是边连通分量
1.电力
信息学奥赛一本通(C++版)在线评测系统 (ssoier.cn)
1.统计连通块的个数cnt
2.枚举从那个连通块中删,在枚举连通块中删除那个点,假如删除这个点后该连通块变成了s个,则答案就是max(s+cnt-1)
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int N=10010,M=30010; int n,m,ans,root; int h[N],e[M],ne[M],idx; int dfn[N],low[N],timestamp; void add(int a,int b) { e[idx]=b,ne[idx]=h[a],h[a]=idx++; } void tarjan(int u) { dfn[u]=low[u]=++timestamp;//第一次遍历让她们都等于时间戳 int cnt=0;//记录删除割点后的连通块个数 for(int i=h[u];~i;i=ne[i]) { int j=e[i]; if(!dfn[j])//假如没有遍历过 { tarjan(j);//遍历一遍 low[u]=min(low[u],low[j]); if(low[j]>=dfn[u]) cnt++;//假如是割点,则连通块++ } else low[u]=min(low[u],dfn[j]);//更新一遍最小值 } if(u!=root&&cnt) cnt++;//假如不是根,且连通块个数不为0,说明这个也是一个割点,则连通块个数+1 ans=max(ans,cnt);//更新一遍最大值 } int main() { while(scanf("%d%d",&n,&m),n||m) { memset(dfn,0,sizeof dfn);//清空,因为dfn兼具判重的作用 memset(h,-1,sizeof h);//清空 idx=timestamp=0;//清空状态 while(m--) { int a,b; scanf("%d%d",&a,&b); add(a,b),add(b,a); } ans=0; int cnt=0; for(root=0;root<n;root++)//枚举每个为根 if(!dfn[root])//假如不在任何一个连通块中 { cnt++;//则新开个连通块 tarjan(root);//遍历一遍 } printf("%d\n",cnt+ans-1); } return 0; }
2.矿场搭建
题意:给定一个无向图,问最少在几个点上设置出口,可以使得不管哪个点坍塌,其余所有点都可以与某个出口连通
#include<bits/stdc++.h> using namespace std; typedef unsigned long long ull; const int N=1010,M=510; int n,m,root; int h[N],e[M],ne[M],idx; int dfn[N],low[N],timestamp; int stk[N],top; int id[N],dcc_cnt; vector<int>dcc[N]; bool cut[N]; void add(int a,int b) { e[idx]=b,ne[idx]=h[a],h[a]=idx++; } void tarjan(int u) { dfn[u]=low[u]=++timestamp;//第一次遍历让她们都等于时间戳 stk[++top]=u; if(u==root&&h[u]==-1)//假如是孤立点 { dcc_cnt++; dcc[dcc_cnt].push_back(u); return; } int cnt=0;//记录子树的个数 for(int i=h[u];~i;i=ne[i]) { int j=e[i]; if(!dfn[j])//假如没有遍历过 { tarjan(j);//遍历一遍 low[u]=min(low[u],low[j]); if(low[j]>=dfn[u]) { cnt++;//子树加1 if(u!=root||cnt>1) cut[u]=true;//假如不为根或者子树为>1则为割点 ++dcc_cnt; int y;//求割点中的点,也即点连通块的点,并放进队列中去 do { y=stk[top--]; dcc[dcc_cnt].push_back(y); }while(y!=j); dcc[dcc_cnt].push_back(u); } } else low[u]=min(low[u],dfn[j]);//更新一遍最小值 } } int main() { int T=1; while(cin>>m,m) { for(int i=1;i<=dcc_cnt;i++) dcc[i].clear();//清空 idx=n=timestamp=top=dcc_cnt=0;//初始化 memset(cut,0,sizeof cut);//清空割点 memset(dfn,0,sizeof dfn);//清空,因为dfn兼具判重的作用 memset(h,-1,sizeof h);//清空 while(m--) { int a,b; scanf("%d%d",&a,&b); n=max(n,max(a,b)); add(a,b),add(b,a); } for(root=1;root<=n;root++)//枚举每个为根 if(!dfn[root])//假如不在任何一个连通块中 tarjan(root);//遍历一遍 int res=0; ull num=1; for(int i=1;i<=dcc_cnt;i++)//枚举每一个连通块 { int cnt=0; int len=dcc[i].size(); for(int j=0;j<len;j++)//枚举该连通块的点 if(cut[dcc[i][j]])//假如是割点,则割点++ cnt++; if(cnt==0&&len>1) res+=2,num*=len*(len-1)/2;//假如不是割点且点的数量大于1 else if(cnt==0) res++;//假如不是割点点的数量为1,则为孤立点 else if(cnt==1) res++,num*=len-1;//假如有一个割点 } printf("Case %d: %d %llu\n",T++,res,num); } return 0; }