强连通分量
模板:(output用来在原来不是强连通的基础上求加多少边变为强连通)
矩阵存储
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<algorithm> #define MAXV 110 #define min(a,b) (a>b?b:a) #define max(a,b) (a>b?a:b) int n,map[MAXV][MAXV],outdegree[MAXV],indegree[MAXV]; int dfn[MAXV]; //第一次访问的步数 int low[MAXV]; //子树中最早的步数 int stap[MAXV],stop; //模拟栈 bool instack[MAXV]; //是否在栈中 int count; //记录连通分量的个数 int cnt; //记录搜索步数 int belong[MAXV]; //属于哪个连通分量 void init(){ count=stop=cnt=0; memset(instack,false,sizeof(instack)); memset(map,0,sizeof(map)); memset(dfn,0,sizeof(dfn)); } void tarjan(int x){ int i; dfn[x]=low[x]=++cnt; stap[stop++]=x; instack[x]=true; for(i=1;i<=n;i++){ if(!map[x][i]) continue; if(!dfn[i]){ tarjan(i); low[x]=min(low[i],low[x]); }else if(instack[i]) low[x]=min(dfn[i],low[x]); //与x相连,但是i已经被访问过,且还在栈中 //用子树节点更新节点第一次出现的时间 } if(low[x]==dfn[x]){ count++; while(1){ int tmp=stap[--stop]; belong[tmp]=count; instack[tmp]=false; if(tmp==x) break; } } } void output(){ int i,j,inzero=0,outzero=0; for(i=1;i<=n;i++){ indegree[i]=outdegree[i]=0; } for(i=1;i<=n;i++) //找连通分量入度与出度 for(j=1;j<=n;j++) if(map[i][j] && belong[i]!=belong[j]){ indegree[belong[j]]++; outdegree[belong[i]]++; } for(i=1;i<=count;i++){ //找入度与出度为0的点 if(!indegree[i]) inzero++; if(!outdegree[i]) outzero++; } if(count==1) //只有1个结点要特判 printf("1\n0\n"); else printf("%d\n%d\n",inzero,max(inzero,outzero)); } int main(){ int i,a; while(~scanf("%d",&n)){ init(); for(i=1;i<=n;i++){ while(scanf("%d",&a) && a) map[i][a]=1; } for(i=1;i<=n;i++) if(!dfn[i]) tarjan(i); output(); } return 0; }邻接表存储:
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<algorithm> #define MOD 100000 #define inf 1<<29 #define LL long long #define MAXN 20010 #define MAXM = 50010 using namespace std; struct Edge { int to,next; bool cut; } edge[MAXN]; int head[MAXN],tot; int low[MAXN],DFN[MAXN],belong[MAXN];///belong 的值为1-block int index,top,fenzhiNum; int block ; ///强连通分量 bool inStack[MAXN]; int bridgeNum; ///桥的数目 int stack[MAXN]; int vis[MAXN]; int inans,outans; int outdu[MAXN]; int indu[MAXN]; void addedge(int u,int v) { edge[tot].to = v; edge[tot].next = head[u]; edge[tot].cut = false; head[u] = tot++ ; } void ini(){ index = block = top = fenzhiNum = 0; inans = 0, outans = 0 ; memset(DFN,0,sizeof(DFN)); memset(inStack,false,sizeof(inStack)); memset(vis,0,sizeof(vis)); memset(outdu,0,sizeof(outdu)); memset(indu,0,sizeof(indu)); memset(head,-1,sizeof(head)); } void Tarjan(int u) { vis[u] = true; int v; low[u] = DFN[u] = ++index; stack[top++] = u; inStack[u] = true; for(int i=head[u] ; i!=-1 ; i=edge[i].next) { v = edge[i].to; //if( v == pre ) continue; ///因为是无向图,所以两条是双向的,所以只遍历一条就够了 if( !DFN[v] ) { Tarjan(v ); if(low[u]>low[v]) low[u] = low[v]; if(low[v] > DFN[u] ){ bridgeNum++; edge[i].cut = true; //edge[i^1].cut = true; ///将两条双向边都设置为桥 } } else if( inStack[v] && low[u] > DFN[v]) low[u] = DFN[v]; } if(low[u] == DFN[u]) { block++; do { v=stack[--top]; ///清空当前强连通分量栈 必须清空 inStack[v] = false; belong[v]=block; ///v节点都编号为block 也就是这是一个块 } while(v!=u); } } void solve(int N) { for(int i=1;i<=N;i++) if(!vis[i]) Tarjan(i); for(int i=1; i<=N ; i++){ ///缩点 for(int j=head[i] ; j!=-1 ; j=edge[j].next) if( belong[i]!=belong[ edge[j].to ] ) indu[ belong[ edge[j].to ] ]++,outdu[ belong[i] ]++ ; } for(int i=1;i<=block ;i++) if(indu[i] == 0) inans++; for(int i=1;i<=block ;i++) if(outdu[i] == 0) outans++; // printf("indu=%d,outdu=%d\n",inans,outans); if(block == 1) printf("1\n0\n"); else printf("%d\n%d\n",inans,max(inans,outans)); //printf("%d\n",(ans+1)/2 ); } int main () { int n,m; while(~scanf("%d",&n)) { int u,v,mark=0; tot=0; ini(); for(int i=1; i<=n; i++) { while(scanf("%d",&u)&&u!=0){ mark=0; for(int j=head[i] ; j!=-1 ; j=edge[j].next) ///去重边 if(edge[j].to == u){ mark = 1; break; } if(!mark) addedge(i,u); } } solve(n); } return 0; }
割点与桥
模板:
/* * 求 无向图 的割点和桥 * 可以找出割点和桥,求删掉每个点后增加的连通块。 * 需要注意重边的处理,可以先用矩阵存,再转邻接表,或者进行判重 */ const int MAXN = 10010; const int MAXM = 2000010; struct Edge { int to,next; bool cut;//是否为桥的标记 }edge[MAXM]; int head[MAXN],tot; int Low[MAXN],DFN[MAXN],Stack[MAXN]; int Index,top; bool Instack[MAXN]; bool cut[MAXN]; ///记录是否是割点 int add_block[MAXN];//删除一个点(i)后增加的连通块 int bridge; void addedge(int u,int v) { edge[tot].to = v;edge[tot].next = head[u];edge[tot].cut = false; head[u] = tot++; } void Tarjan(int u,int pre) ///pre是父节点,用来判断重边 { int v; Low[u] = DFN[u] = ++Index; Stack[top++] = u; Instack[u] = true; int son = 0; int pre_cnt = 0; ///处理重边 ,如果不需要可以去掉 for(int i = head[u];i != -1;i = edge[i].next) { v = edge[i].to; if(v == pre && pre_cnt == 0) { pre_cnt++; continue; } if( !DFN[v] ) { son++; Tarjan(v,u); if(Low[u] > Low[v]) Low[u] = Low[v]; ///桥 ///一条无向边(u,v)是桥,当且仅当(u,v)为树枝边,且满足DFS(u)<Low(v)。 if(Low[v] > DFN[u]) { bridge++; edge[i].cut = true; edge[i^1].cut = true; } //割点 //一个顶点u是割点,当且仅当满足(1)或(2) //(1) u为树根,且u有多于一个子树。 //(2) u不为树根,且满足存在(u,v)为树枝边(或称父子边,即u为v在搜索树中的父亲),使得DFS(u)<=Low(v) if(u != pre && Low[v] >= DFN[u])//不是树根 { cut[u] = true; add_block[u]++; } } else if( Low[u] > DFN[v]) Low[u] = DFN[v]; } //树根,分支数大于1 if(u == pre && son > 1) cut[u] = true; if(u == pre) add_block[u] = son - 1; Instack[u] = false; top--; }
//#pragma comment(linker, "/STACK:102400000,102400000" #include<cstdio> #include<cstring> #include<cmath> #include<cstdlib> #include<iostream> #include<algorithm> #include<vector> #include<map> #include<queue> #include<stack> #include<string> #include<map> #include<set> #include<ctime> #define eps 1e-6 #define MAX 100005 #define INF 0x3f3f3f3f #define LL long long #define pii pair<int,int> #define rd(x) scanf("%d",&x) #define rd2(x,y) scanf("%d%d",&x,&y) using namespace std; const int MAXN = 200010;//点数 const int MAXM = 2000010;//边数,因为是无向图,所以这个值要*2 struct Edge { int to,next; bool cut;//是否是桥标记 }edge[MAXM]; int head[MAXN],tot; int Low[MAXN],DFN[MAXN],Stack[MAXN],Belong[MAXN];//Belong数组的值是1~block int Index,top; int block;//边双连通块数 bool Instack[MAXN]; int bridge;//桥的数目 void addedge(int u,int v) { edge[tot].to = v;edge[tot].next = head[u];edge[tot].cut=false; head[u] = tot++; } void Tarjan(int u,int pre) { int v; Low[u] = DFN[u] = ++Index; Stack[top++] = u; int pre_cnt=0; ///处理重边 Instack[u] = true; for(int i = head[u];i != -1;i = edge[i].next) { v = edge[i].to; if(v == pre && pre_cnt == 0 ){ pre_cnt ++; continue; } if( !DFN[v] ) { Tarjan(v,u); if( Low[u] > Low[v] )Low[u] = Low[v]; if(Low[v] > DFN[u]) { bridge++; edge[i].cut = true; edge[i^1].cut = true; } } else if( Instack[v] && Low[u] > DFN[v] ) Low[u] = DFN[v]; } if(Low[u] == DFN[u]) { block++; do { v = Stack[--top]; Instack[v] = false; Belong[v] = block; } while( v!=u ); } } void init() { tot = 0; memset(head,-1,sizeof(head)); } int du[MAXN];//缩点后形成树,每个点的度数 vector<int>vec[MAXN]; int dep[MAXN]; void dfs(int u) { for(int i = 0;i < vec[u].size();i++) { int v = vec[u][i]; if(dep[v]!=-1)continue; dep[v]=dep[u]+1; dfs(v); } } void solve(int n) { memset(DFN,0,sizeof(DFN)); memset(Instack,false,sizeof(Instack)); Index = top = block = 0; Tarjan(1,0); for(int i = 1;i <= block;i++) vec[i].clear(); for(int i = 1;i <= n;i++) for(int j = head[i];j != -1;j = edge[j].next) if(edge[j].cut) { vec[Belong[i]].push_back(Belong[edge[j].to]); } memset(dep,-1,sizeof(dep)); dep[1]=0; dfs(1); ///第一次dfs找距离1节点最远的节点k int k = 1; for(int i = 1;i <= block;i++) if(dep[i]>dep[k]) k = i; memset(dep,-1,sizeof(dep)); dep[k]=0; dfs(k); ///第二次dfs找出距离k最远的节点,也就是树的直径 int ans = 0; for(int i = 1;i <= block;i++) ans = max(ans,dep[i]); printf("%d\n",block-1-ans); } int main() { //freopen("in.txt","r",stdin); //freopen("out.txt","w",stdout); int n,m; int u,v; while(scanf("%d%d",&n,&m)==2) { if(n==0 && m==0)break; init(); for(int i = 0;i < m;i++) { scanf("%d%d",&u,&v); addedge(u,v); addedge(v,u); } solve(n); } return 0; }