算法笔记--强连通分量分解
Kosaraju算法
详见《挑战程序设计竞赛》p320
模板:
const int N=1e5+5; int n,m; vector<int>g[N]; vector<int>rg[N]; vector<int>vs; bool vis[N]; int cmp[N]; void add_edge(int u,int v) { g[u].pb(v); rg[v].pb(u); } void dfs(int u) { vis[u]=true; for(int i=0;i<g[u].size();i++) if(!vis[g[u][i]])dfs(g[u][i]); vs.pb(u); } void rdfs(int u,int k) { vis[u]=true; cmp[u]=k; for(int i=0;i<rg[u].size();i++) if(!vis[rg[u][i]])rdfs(rg[u][i],k); } int scc() { mem(vis,false); vs.clear(); for(int i=1;i<=n;i++)if(!vis[i])dfs(i); mem(vis,false); int k=0; for(int i=vs.size()-1;i>=0;i--)if(!vis[vs[i]])rdfs(vs[i],k++); return k; }
Tarjan算法模板:
const int N = 1e5 + 5; vector<int> g[N]; int low[N], dfn[N], stk[N], cmp[N], cnt = 0, top = 0, tot = 0; bool vis[N]; void tarjan(int u) { low[u] = dfn[u] = ++cnt; stk[++top] = u; vis[u] = true; //标记是否在栈中 for (int v : g[u]) { if(!dfn[v]) { tarjan(v); low[u] = min(low[u], low[v]); } else if(vis[v]) low[u] = min(low[u], dfn[v]); } if(low[u] == dfn[u]) { cmp[u] = ++tot; vis[u] = false; while(stk[top] != u) { cmp[stk[top]] = tot; vis[stk[top--]] = false; } top--; } }
代码:
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<cmath> #include<vector> using namespace std; #define ll long long #define pb push_back #define mem(a,b) memset(a,b,sizeof(a)) const int N=1e5+5; int n,m; vector<int>g[N]; vector<int>rg[N]; vector<int>vs; bool vis[N]; int cmp[N]; void add_edge(int u,int v) { g[u].pb(v); rg[v].pb(u); } void dfs(int u) { vis[u]=true; for(int i=0;i<g[u].size();i++) if(!vis[g[u][i]])dfs(g[u][i]); vs.pb(u); } void rdfs(int u,int k) { vis[u]=true; cmp[u]=k; for(int i=0;i<rg[u].size();i++) if(!vis[rg[u][i]])rdfs(rg[u][i],k); } int scc() { mem(vis,false); vs.clear(); for(int i=1;i<=n;i++)if(!vis[i])dfs(i); mem(vis,false); int k=0; for(int i=vs.size()-1;i>=0;i--)if(!vis[vs[i]])rdfs(vs[i],k++); return k; } int main() { ios::sync_with_stdio(false); cin.tie(0); cin>>n>>m; int u,v; for(int i=0;i<m;i++)cin>>u>>v,add_edge(u,v); int t=scc(); int ans=0; for(int i=1;i<=n;i++)if(cmp[i]==t-1)ans++,u=i; mem(vis,false); rdfs(u,0); for(int i=1;i<=n;i++)if(!vis[i])ans=0; cout<<ans<<endl; return 0; }
例题2:POJ 1236 Network of Schools
思路:统计出入度,第一个答案输出强连通分量入度为0的个数,第二个答案输出max(入度为0个数,出度为0的个数),如果只有一个强连通,就是0。
代码:
#include<iostream> #include<cstdio> #include<cmath> #include<cstring> #include<algorithm> #include<vector> using namespace std; #define ll long long #define pb push_back #define mem(a,b) memset(a,b,sizeof(a)) const int N=105; vector<int>g[N]; vector<int>rg[N]; vector<int>vs; bool vis[N]; int cmp[N]; int in[N]={0}; int out[N]={0}; int n,a; void add_edge(int u,int v) { g[u].pb(v); rg[v].pb(u); } void dfs(int u) { vis[u]=true; for(int i=0;i<g[u].size();i++)if(!vis[g[u][i]])dfs(g[u][i]); vs.pb(u); } void rdfs(int u,int k) { vis[u]=true; cmp[u]=k; for(int i=0;i<rg[u].size();i++)if(!vis[rg[u][i]])rdfs(rg[u][i],k); } int scc() { mem(vis,false); vs.clear(); for(int i=1;i<=n;i++)if(!vis[i])dfs(i); mem(vis,false); int k=0; for(int i=vs.size()-1;i>=0;i--)if(!vis[vs[i]])rdfs(vs[i],k++); return k; } int main() { ios::sync_with_stdio(false); cin.tie(0); cin>>n; for(int i=1;i<=n;i++) { while(cin>>a&&a)add_edge(i,a); } int t=scc(); for(int i=1;i<=n;i++) { for(int j=0;j<g[i].size();j++) if(cmp[i]!=cmp[g[i][j]])out[cmp[i]]++,in[cmp[g[i][j]]]++; } int in1=0,out1=0; for(int i=0;i<t;i++) { //cout<<in[i]<<' '<<out[i]<<endl; if(in[i]==0)in1++; if(out[i]==0)out1++; } cout<<in1<<endl; if(t==1)cout<<0<<endl; else cout<<max(in1,out1)<<endl; return 0; }
思路:能结婚的两个人肯定在一个强连通分量里。
代码:
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<algorithm> #include<vector> using namespace std; #define ll long long #define pb push_back #define mem(a,b) memset(a,b,sizeof(a)) const int N=4e3+5; vector<int>g[N]; vector<int>rg[N]; vector<int>vs; vector<int>ans; bool vis[N]; bool mp[N/2][N/2]; int cmp[N]; int n,k,a; void add_edge(int u,int v) { g[u].pb(v); rg[v].pb(u); } void dfs(int u) { vis[u]=true; for(int i=0;i<g[u].size();i++)if(!vis[g[u][i]])dfs(g[u][i]); vs.pb(u); } void rdfs(int u,int k) { vis[u]=true; cmp[u]=k; for(int i=0;i<rg[u].size();i++)if(!vis[rg[u][i]])rdfs(rg[u][i],k); } int scc() { mem(vis,false); vs.clear(); for(int i=1;i<=2*n;i++)if(!vis[i])dfs(i); int k=0; mem(vis,false); for(int i=vs.size()-1;i>=0;i--)if(!vis[vs[i]])rdfs(vs[i],k++); return k; } int main() { /*ios::sync_with_stdio(false); cin.tie(0);*/ scanf("%d",&n); for(int i=1;i<=n;i++) { scanf("%d",&k); while(k--) { scanf("%d",&a); add_edge(i,n+a); mp[i][a]=true; } } for(int i=1;i<=n;i++)scanf("%d",&a),add_edge(n+a,i); int t=scc(); //cout<<t<<endl; for(int i=1;i<=n;i++) { ans.clear(); for(int j=1;j<=n;j++) if(cmp[j+n]==cmp[i]&&mp[i][j])ans.pb(j); printf("%d ",ans.size()); for(int j=0;j<ans.size()-1;j++)printf("%d ",ans[j]); printf("%d\n",ans[ans.size()-1]); } return 0; }
例题4:HDU 1269 迷宫城堡
坑点:当n不为0时,m为0不需要退出输入,因为此时是个非联通图。
代码:
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<algorithm> #include<vector> using namespace std; #define ll long long #define pb push_back #define mem(a,b) memset(a,b,sizeof(a)) const int N=1e4+5; vector<int>g[N]; vector<int>rg[N]; vector<int>vs; bool vis[N]; int cmp[N]; int n,m,u,v; void add_edge(int u,int v) { g[u].pb(v); rg[v].pb(u); } void dfs(int u) { vis[u]=true; for(int i=0;i<g[u].size();i++)if(!vis[g[u][i]])dfs(g[u][i]); vs.pb(u); } void rdfs(int u,int k) { vis[u]=true; cmp[u]=k; for(int i=0;i<rg[u].size();i++)if(!vis[rg[u][i]])rdfs(rg[u][i],k); } int scc() { mem(vis,false); vs.clear(); for(int i=1;i<=n;i++)if(!vis[i])dfs(i); int k=0; mem(vis,false); for(int i=vs.size()-1;i>=0;i--)if(!vis[vs[i]])rdfs(vs[i],k++); return k; } int main() { ios::sync_with_stdio(false); cin.tie(0); while(cin>>n>>m&&(n||m)) { for(int i=1;i<=n;i++)g[i].clear(),rg[i].clear(); while(m--) { cin>>u>>v; add_edge(u,v); } int t=scc(); //cout<<t<<endl; if(t==1)cout<<"Yes"<<endl; else cout<<"No"<<endl; } return 0; }
例题5:HDU 2767 Proving Equivalences
思路:见例题2。
代码:
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<algorithm> #include<vector> using namespace std; #define ll long long #define pb push_back #define mem(a,b) memset(a,b,sizeof(a)) const int N=2e4+5; vector<int>g[N]; vector<int>rg[N]; vector<int>vs; bool vis[N]; int cmp[N]; int in[N]; int out[N]; int n,m,u,v; void add_edge(int u,int v) { g[u].pb(v); rg[v].pb(u); } void dfs(int u) { vis[u]=true; for(int i=0;i<g[u].size();i++)if(!vis[g[u][i]])dfs(g[u][i]); vs.pb(u); } void rdfs(int u,int k) { vis[u]=true; cmp[u]=k; for(int i=0;i<rg[u].size();i++)if(!vis[rg[u][i]])rdfs(rg[u][i],k); } int scc() { mem(vis,false); vs.clear(); for(int i=1;i<=n;i++)if(!vis[i])dfs(i); int k=0; mem(vis,false); for(int i=vs.size()-1;i>=0;i--)if(!vis[vs[i]])rdfs(vs[i],k++); return k; } int main() { ios::sync_with_stdio(false); cin.tie(0); int t; cin>>t; while(t--) { cin>>n>>m; for(int i=1;i<=n;i++)g[i].clear(),rg[i].clear(); mem(in,0); mem(out,0); while(m--) { cin>>u>>v; add_edge(u,v); } int t=scc(); for(int i=1;i<=n;i++) { for(int j=0;j<g[i].size();j++) { if(cmp[i]!=cmp[g[i][j]]) out[cmp[i]]++,in[cmp[g[i][j]]]++; } } int _in=0,_out=0; for(int i=0;i<t;i++) { if(in[i]==0)_in++; if(out[i]==0)_out++; } if(t==1)cout<<0<<endl; else cout<<max(_in,_out)<<endl; } return 0; }
思路:计算一下联系每一个强连通的最小话费。
代码:
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<algorithm> #include<vector> using namespace std; #define ll long long #define pb push_back #define mem(a,b) memset(a,b,sizeof(a)) const int N=2e4+5; const int INF=0x3f3f3f3f; vector<int>g[N]; vector<int>rg[N]; vector<int>vs; bool vis[N]; int cmp[N]; int in[N]; int out[N]; int mn[N]; int cost[N]; int n,m,u,v; void add_edge(int u,int v) { g[u].pb(v); rg[v].pb(u); } void dfs(int u) { vis[u]=true; for(int i=0;i<g[u].size();i++)if(!vis[g[u][i]])dfs(g[u][i]); vs.pb(u); } void rdfs(int u,int k) { vis[u]=true; cmp[u]=k; for(int i=0;i<rg[u].size();i++)if(!vis[rg[u][i]])rdfs(rg[u][i],k); } int scc() { mem(vis,false); vs.clear(); for(int i=1;i<=n;i++)if(!vis[i])dfs(i); int k=0; mem(vis,false); for(int i=vs.size()-1;i>=0;i--)if(!vis[vs[i]])rdfs(vs[i],k++); return k; } int main() { ios::sync_with_stdio(false); cin.tie(0); while(cin>>n>>m) { for(int i=1;i<=n;i++)cin>>cost[i]; for(int i=1;i<=n;i++)g[i].clear(),rg[i].clear(); mem(in,0); mem(out,0); mem(mn,INF); while(m--) { cin>>u>>v; add_edge(u,v); } int t=scc(); for(int i=1;i<=n;i++) { for(int j=0;j<g[i].size();j++) { if(cmp[i]!=cmp[g[i][j]]) out[cmp[i]]++,in[cmp[g[i][j]]]++; } mn[cmp[i]]=min(mn[cmp[i]],cost[i]); } ll ans=0; int cnt=0; for(int i=0;i<t;i++) { if(in[i]==0)ans+=mn[i],cnt++; } cout<<cnt<<' '<<ans<<endl; } return 0; }
例题7:POJ 2553 The Bottom of Graph
思路:把英文读懂了就明白怎么做了:http://poj.org/showmessage?message_id=162296,其实就是找所有出度为0的强连通分支
代码:
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<algorithm> #include<vector> using namespace std; #define ll long long #define pb push_back #define mem(a,b) memset(a,b,sizeof(a)) const int N=2e4+5; const int INF=0x3f3f3f3f; vector<int>g[N]; vector<int>rg[N]; vector<int>vs; vector<int>ans; bool vis[N]; int cmp[N]; int in[N]; int out[N]; int n,m,u,v; void add_edge(int u,int v) { g[u].pb(v); rg[v].pb(u); } void dfs(int u) { vis[u]=true; for(int i=0;i<g[u].size();i++)if(!vis[g[u][i]])dfs(g[u][i]); vs.pb(u); } void rdfs(int u,int k) { vis[u]=true; cmp[u]=k; for(int i=0;i<rg[u].size();i++)if(!vis[rg[u][i]])rdfs(rg[u][i],k); } int scc() { mem(vis,false); vs.clear(); for(int i=1;i<=n;i++)if(!vis[i])dfs(i); int k=0; mem(vis,false); for(int i=vs.size()-1;i>=0;i--)if(!vis[vs[i]])rdfs(vs[i],k++); return k; } int main() { ios::sync_with_stdio(false); cin.tie(0); while(cin>>n&&n&&cin>>m) { for(int i=1;i<=n;i++)g[i].clear(),rg[i].clear(); mem(in,0); mem(out,0); for(int i=0;i<m;i++) { cin>>u>>v; add_edge(u,v); } int t=scc(); ans.clear(); for(int i=1;i<=n;i++) { for(int j=0;j<g[i].size();j++) if(cmp[i]!=cmp[g[i][j]]) { out[cmp[i]]++; in[cmp[g[i][j]]]++; } } for(int i=1;i<=n;i++) { if(out[cmp[i]]==0)ans.pb(i); } for(int i=0;i<ans.size();i++) { cout<<ans[i]; if(i!=ans.size()-1)cout<<' '; } cout<<endl; } return 0; }
例题8:POJ 2762 Going from u to v or from v to u?
思路:强连通缩点+拓扑排序,顺便学了一波队列求拓扑排序http://blog.csdn.net/lisonglisonglisong/article/details/45543451
删边后入度为0的点不能超过1个,详见:http://www.cnblogs.com/scau20110726/archive/2013/05/23/3094495.html
代码1(邻接矩阵保存缩点后的DAG):
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<algorithm> #include<vector> #include<queue> using namespace std; #define ll long long #define pb push_back #define mem(a,b) memset(a,b,sizeof(a)) const int N=1e3+5; vector<int>g[N]; vector<int>rg[N]; vector<int>vs; bool vis[N]; int cmp[N]; int in[N]; int out[N]; bool newg[N][N]; int n,m,u,v; void add_edge(int u,int v) { g[u].pb(v); rg[v].pb(u); } void dfs(int u) { vis[u]=true; for(int i=0;i<g[u].size();i++)if(!vis[g[u][i]])dfs(g[u][i]); vs.pb(u); } void rdfs(int u,int k) { vis[u]=true; cmp[u]=k; for(int i=0;i<rg[u].size();i++)if(!vis[rg[u][i]])rdfs(rg[u][i],k); } int scc() { mem(vis,false); vs.clear(); for(int i=1;i<=n;i++)if(!vis[i])dfs(i); int k=0; mem(vis,false); for(int i=vs.size()-1;i>=0;i--)if(!vis[vs[i]])rdfs(vs[i],k++); return k; } void init() { for(int i=1;i<=n;i++)g[i].clear(),rg[i].clear(); mem(newg,false); mem(in,0); mem(out,0); } bool topo_sort(int t) { queue<int>q; int cnt=0; for(int i=0;i<t;i++)if(in[i]==0)cnt++,q.push(i); if(cnt>1)return false; while(!q.empty()) { cnt=0; int u=q.front(); q.pop(); for(int i=0;i<t;i++) { if(newg[u][i]) { in[i]--; if(in[i]==0)q.push(i),cnt++; } } if(cnt>1)return false; } return true; } int main() { ios::sync_with_stdio(false); cin.tie(0); int t; cin>>t; while(t--) { cin>>n>>m; init(); for(int i=0;i<m;i++) { cin>>u>>v; add_edge(u,v); } int tt=scc(); for(int i=1;i<=n;i++) { for(int j=0;j<g[i].size();j++) { if(cmp[i]!=cmp[g[i][j]])newg[cmp[i]][cmp[g[i][j]]]=true; } } for(int i=0;i<tt;i++) { for(int j=0;j<tt;j++) if(newg[i][j])out[i]++,in[j]++; } if(topo_sort(tt))cout<<"Yes"<<endl; else cout<<"No"<<endl; } return 0; }
代码2(邻接表保存缩点后的DAG,有重边)
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<algorithm> #include<vector> #include<queue> using namespace std; #define ll long long #define pb push_back #define mem(a,b) memset(a,b,sizeof(a)) const int N=1e3+5; vector<int>g[N]; vector<int>rg[N]; vector<int>newg[N]; vector<int>vs; bool vis[N]; int cmp[N]; int in[N]; int out[N]; int n,m,u,v; void add_edge(int u,int v) { g[u].pb(v); rg[v].pb(u); } void dfs(int u) { vis[u]=true; for(int i=0;i<g[u].size();i++)if(!vis[g[u][i]])dfs(g[u][i]); vs.pb(u); } void rdfs(int u,int k) { vis[u]=true; cmp[u]=k; for(int i=0;i<rg[u].size();i++)if(!vis[rg[u][i]])rdfs(rg[u][i],k); } int scc() { mem(vis,false); vs.clear(); for(int i=1;i<=n;i++)if(!vis[i])dfs(i); int k=0; mem(vis,false); for(int i=vs.size()-1;i>=0;i--)if(!vis[vs[i]])rdfs(vs[i],k++); return k; } void init() { for(int i=0;i<=n;i++)g[i].clear(),rg[i].clear(),newg[i].clear(); mem(in,0); mem(out,0); } bool topo_sort(int t) { queue<int>q; int cnt=0; for(int i=0;i<t;i++)if(in[i]==0)cnt++,q.push(i); if(cnt>1)return false; while(!q.empty()) { cnt=0; int u=q.front(); q.pop(); for(int i=0;i<newg[u].size();i++) { int v=newg[u][i]; in[v]--; if(in[v]==0)cnt++,q.push(v); } if(cnt>1)return false; } return true; } int main() { ios::sync_with_stdio(false); cin.tie(0); int t; cin>>t; while(t--) { cin>>n>>m; init(); for(int i=0;i<m;i++) { cin>>u>>v; add_edge(u,v); } int tt=scc(); for(int i=1;i<=n;i++) { for(int j=0;j<g[i].size();j++) { if(cmp[i]!=cmp[g[i][j]]) { newg[cmp[i]].pb(cmp[g[i][j]]); out[cmp[i]]++; in[cmp[g[i][j]]]++; } } } if(topo_sort(tt))cout<<"Yes"<<endl; else cout<<"No"<<endl; } return 0; }
例题9:POJ 3169 Father Christmas flymouse
思路:强连通缩点成DAG,对于每一个入度为0的节点,dfs求出最大的ans。
代码:
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<algorithm> #include<vector> using namespace std; #define ll long long #define pb push_back #define mem(a,b) memset(a,b,sizeof(a)) const int N=3e4+5; vector<int>g[N]; vector<int>rg[N]; vector<int>ng[N]; vector<int>vs; bool vis[N]; int a[N]; int in[N]; int cmp[N]; int value[N]; int n,m,u,v; void init() { for(int i=0;i<=n;i++)g[i].clear(),rg[i].clear(),ng[i].clear(); mem(in,0); mem(value,0); } void add_edge(int u,int v) { g[u].pb(v); rg[v].pb(u); } void dfs(int u) { vis[u]=true; for(int i=0;i<g[u].size();i++)if(!vis[g[u][i]])dfs(g[u][i]); vs.pb(u); } void rdfs(int u,int k) { vis[u]=true; cmp[u]=k; for(int i=0;i<rg[u].size();i++)if(!vis[rg[u][i]])rdfs(rg[u][i],k); } int scc() { mem(vis,false); vs.clear(); for(int i=0;i<n;i++)if(!vis[i])dfs(i); int k=0; mem(vis,false); for(int i=vs.size()-1;i>=0;i--)if(!vis[vs[i]])rdfs(vs[i],k++); return k; } int DFS(int u) { if(!vis[u]) { int t=0; vis[u]=true; for(int i=0;i<ng[u].size();i++) t=max(t,DFS(ng[u][i])); value[u]+=t; } return value[u]; } int main() { ios::sync_with_stdio(false); cin.tie(0); while(cin>>n>>m) { init(); for(int i=0;i<n;i++)cin>>a[i]; for(int i=0;i<m;i++)cin>>u>>v,add_edge(u,v); int t=scc(); for(int i=0;i<n;i++) { for(int j=0;j<g[i].size();j++) if(cmp[i]!=cmp[g[i][j]])ng[cmp[i]].pb(cmp[g[i][j]]),in[cmp[g[i][j]]]++; if(a[i]>0)value[cmp[i]]+=a[i]; } mem(vis,false); int ans=0; for(int i=0;i<t;i++)if(in[i]==0)ans=max(ans,DFS(i)); cout<<ans<<endl; //cout<<value[0]<<endl; } return 0; }