专题训练之强连通分量和2-sat
Posted jackyan
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了专题训练之强连通分量和2-sat相关的知识,希望对你有一定的参考价值。
tarjan模板
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 using namespace std; 5 const int maxn=20010; 6 const int maxm=50010; 7 struct edge{ 8 int to,nxt; 9 }edge[maxm]; 10 int head[maxn],tot; 11 int low[maxn],dfn[maxn],stack[maxn],belong[maxn]; 12 int index,top; 13 int scc; 14 bool vis[maxn]; 15 int num[maxn]; 16 17 void addedge(int u,int v) 18 { 19 edge[tot].to=v; 20 edge[tot].nxt=head[u]; 21 head[u]=tot++; 22 } 23 24 void tarjan(int u) 25 { 26 int v; 27 low[u]=dfn[u]=++index; 28 stack[top++]=u; 29 vis[u]=true; 30 for ( int i=head[u];i!=-1;i=edge[i].nxt ) { 31 v=edge[i].to; 32 if ( !dfn[v] ) { 33 tarjan(v); 34 low[u]=min(low[u],low[v]); 35 } 36 else if ( vis[v] ) low[u]=min(low[u],dfn[v]); 37 } 38 if ( low[u]==dfn[u] ) { 39 scc++; 40 do { 41 v=stack[--top]; 42 vis[v]=false; 43 belong[v]=scc; 44 num[scc]++; 45 } 46 while ( v!=u ); 47 } 48 } 49 50 void solve(int N) 51 { 52 memset(dfn,0,sizeof(dfn)); 53 memset(vis,false,sizeof(vis)); 54 memset(num,0,sizeof(num)); 55 index=scc=top=0; 56 for ( int i=1;i<=N;i++ ) { 57 if ( !dfn[i] ) tarjan(i); 58 } 59 } 60 61 void init() 62 { 63 tot=0; 64 memset(head,-1,sizeof(head)); 65 }
1.(HDOJ1269)http://acm.hdu.edu.cn/showproblem.php?pid=1269
分析:裸题,判断scc是否为1即可
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 using namespace std; 5 const int maxn=1e4+10; 6 const int maxm=1e5+10; 7 struct edge{ 8 int to,nxt; 9 }edge[maxm]; 10 int head[maxn],tot; 11 int low[maxn],dfn[maxn],stack[maxn],belong[maxn]; 12 int index,top; 13 int scc,n; 14 bool vis[maxn]; 15 int num[maxn]; 16 17 void addedge(int u,int v) 18 { 19 edge[tot].to=v; 20 edge[tot].nxt=head[u]; 21 head[u]=tot++; 22 } 23 24 void tarjan(int u) 25 { 26 int v; 27 low[u]=dfn[u]=++index; 28 stack[top++]=u; 29 vis[u]=true; 30 for ( int i=head[u];i!=-1;i=edge[i].nxt ) { 31 v=edge[i].to; 32 if ( !dfn[v] ) { 33 tarjan(v); 34 low[u]=min(low[u],low[v]); 35 } 36 else if ( vis[v] ) low[u]=min(low[u],dfn[v]); 37 } 38 if ( low[u]==dfn[u] ) { 39 scc++; 40 do { 41 v=stack[--top]; 42 vis[v]=false; 43 belong[v]=scc; 44 num[scc]++; 45 } 46 while ( v!=u ); 47 } 48 } 49 50 void solve() 51 { 52 memset(dfn,0,sizeof(dfn)); 53 memset(vis,false,sizeof(vis)); 54 memset(num,0,sizeof(num)); 55 index=scc=top=0; 56 for ( int i=1;i<=n;i++ ) { 57 if ( !dfn[i] ) tarjan(i); 58 } 59 } 60 61 void init() 62 { 63 tot=0; 64 memset(head,-1,sizeof(head)); 65 } 66 67 int main() 68 { 69 int m,i,j,k,x,y; 70 while ( scanf("%d%d",&n,&m)!=EOF && (n+m) ) { 71 init(); 72 for ( i=0;i<m;i++ ) { 73 scanf("%d%d",&x,&y); 74 addedge(x,y); 75 } 76 solve(); 77 if ( scc==1 ) printf("Yes\n"); 78 else printf("No\n"); 79 } 80 return 0; 81 }
2.(HDOJ1827)http://acm.hdu.edu.cn/showproblem.php?pid=1827
分析:利用tarjan进行缩点得到新的GAD图,然后再根据新图中每个节点的入度和出度进行相应的操作。因为该题需要花费更少,所有只需要求那些所有入度为0的点所需要花费的费用。而对于新图中每个点的花费,可以很方便的在tarjan中进行更新
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 using namespace std; 5 const int maxn=1005; 6 const int maxm=2005; 7 const int inf=1e9; 8 struct edge{ 9 int to,nxt; 10 }edge[maxm]; 11 int head[maxn],tot; 12 int low[maxn],dfn[maxn],stack[maxn],belong[maxn]; 13 int index,top; 14 int scc,n; 15 bool vis[maxn]; 16 int num[maxn]; 17 int cost[maxn],in[maxn],out[maxn],cost_[maxn]; 18 19 void addedge(int u,int v) 20 { 21 edge[tot].to=v; 22 edge[tot].nxt=head[u]; 23 head[u]=tot++; 24 } 25 26 void tarjan(int u) 27 { 28 int v; 29 low[u]=dfn[u]=++index; 30 stack[top++]=u; 31 vis[u]=true; 32 for ( int i=head[u];i!=-1;i=edge[i].nxt ) { 33 v=edge[i].to; 34 if ( !dfn[v] ) { 35 tarjan(v); 36 low[u]=min(low[u],low[v]); 37 } 38 else if ( vis[v] ) low[u]=min(low[u],dfn[v]); 39 } 40 if ( low[u]==dfn[u] ) { 41 scc++; 42 do { 43 v=stack[--top]; 44 vis[v]=false; 45 belong[v]=scc; 46 num[scc]++; 47 cost_[scc]=min(cost_[scc],cost[v]); 48 } 49 while ( v!=u ); 50 } 51 } 52 53 void solve() 54 { 55 memset(dfn,0,sizeof(dfn)); 56 memset(vis,false,sizeof(vis)); 57 memset(num,0,sizeof(num)); 58 for ( int i=1;i<=n;i++ ) cost_[i]=inf; 59 index=scc=top=0; 60 for ( int i=1;i<=n;i++ ) { 61 if ( !dfn[i] ) tarjan(i); 62 } 63 } 64 65 void init() 66 { 67 tot=0; 68 memset(head,-1,sizeof(head)); 69 } 70 71 int main() 72 { 73 int m,i,j,k,x,y,ans,cnt,v; 74 while ( scanf("%d%d",&n,&m)!=EOF ) { 75 init(); 76 for ( i=1;i<=n;i++ ) scanf("%d",&cost[i]); 77 for ( i=1;i<=m;i++ ) { 78 scanf("%d%d",&x,&y); 79 addedge(x,y); 80 } 81 solve(); 82 memset(in,0,sizeof(in)); 83 memset(out,0,sizeof(out)); 84 for ( i=1;i<=n;i++ ) { 85 for ( j=head[i];j!=-1;j=edge[j].nxt ) { 86 v=edge[j].to; 87 if ( belong[i]!=belong[v] ) { 88 in[belong[v]]++; 89 out[belong[i]]++; 90 } 91 } 92 } 93 ans=0; 94 cnt=0; 95 for ( i=1;i<=scc;i++ ) { 96 if ( !in[i] ) { 97 ans+=cost_[i]; 98 cnt++; 99 } 100 } 101 printf("%d %d\n",cnt,ans); 102 } 103 return 0; 104 }
3.(HDOJ2767、HDOJ3836)
http://acm.hdu.edu.cn/showproblem.php?pid=2767
http://acm.hdu.edu.cn/showproblem.php?pid=3836
题意:添加多少条可以使得整个图为一个强连通分量。先求出强连通分量的个数,然后需要添加的边为出度和入度为0中较大的那个值(即没有点的入度和出度为0)。需要特别注意的是本身只有一个强连通分量则直接输出0
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 using namespace std; 5 const int maxn=20050; 6 const int maxm=50010; 7 struct edge{ 8 int to,nxt; 9 }edge[maxm]; 10 int head[maxn],tot; 11 int low[maxn],dfn[maxn],stack[maxn],belong[maxn]; 12 int index,top; 13 int scc; 14 bool vis[maxn]; 15 int num[maxn],in[maxn],out[maxn]; 16 17 void addedge(int u,int v) 18 { 19 edge[tot].to=v; 20 edge[tot].nxt=head[u]; 21 head[u]=tot++; 22 } 23 24 void tarjan(int u) 25 { 26 int v; 27 low[u]=dfn[u]=++index; 28 stack[top++]=u; 29 vis[u]=true; 30 for ( int i=head[u];i!=-1;i=edge[i].nxt ) { 31 v=edge[i].to; 32 if ( !dfn[v] ) { 33 tarjan(v); 34 low[u]=min(low[u],low[v]); 35 } 36 else if ( vis[v] ) low[u]=min(low[u],dfn[v]); 37 } 38 if ( low[u]==dfn[u] ) { 39 scc++; 40 do { 41 v=stack[--top]; 42 vis[v]=false; 43 belong[v]=scc; 44 num[scc]++; 45 } 46 while ( v!=u ); 47 } 48 } 49 50 void solve(int N) 51 { 52 memset(dfn,0,sizeof(dfn)); 53 memset(vis,false,sizeof(vis)); 54 memset(num,0,sizeof(num)); 55 index=scc=top=0; 56 for ( int i=1;i<=N;i++ ) { 57 if ( !dfn[i] ) tarjan(i); 58 } 59 } 60 61 void init() 62 { 63 tot=0; 64 memset(head,-1,sizeof(head)); 65 } 66 67 int main() 68 { 69 int T,i,j,k,n,m,x,y,z,ans,cnt1,cnt2; 70 scanf("%d",&T); 71 while ( T-- ) { 72 init(); 73 scanf("%d%d",&n,&m); 74 while ( m-- ) { 75 scanf("%d%d",&x,&y); 76 addedge(x,y); 77 } 78 solve(n); 79 memset(in,0,sizeof(in)); 80 memset(out,0,sizeof(out)); 81 for ( i=1;i<=n;i++ ) { 82 for ( j=head[i];j!=-1;j=edge[j].nxt ) { 83 int v=edge[j].to; 84 if ( belong[v]!=belong[i] ) { 85 in[belong[v]]++; 86 out[belong[i]]++; 87 } 88 } 89 } 90 cnt1=cnt2=0; 91 for ( i=1;i<=scc;i++ ) { 92 if ( in[i]==0 ) cnt1++; 93 if ( out[i]==0 ) cnt2++; 94 } 95 ans=max(cnt1,cnt2); 96 if ( scc==1 ) ans=0; 97 printf("%d\n",ans); 98 } 99 return 0; 100 }
4.(HDOJ3639)http://acm.hdu.edu.cn/showproblem.php?pid=3639
题意:有n个小孩要相互投票,有m条两人之间的单向的支持关系,求获得支持最多的人的支持数,并要求把这些人的编号(0~n-1)按升序排列输出来。
分析:首先利用tarjan进行缩点构建新图,首先明确得票数最高的人一定在出度为0的强连通分量团体中,所以当前任务就是确定对于每个出度为0的强连通分量团体有多少人能够直接或间接指向它们。这时候就要在新图上构建反向边,对于出度为0的点i进行搜索,经过的所有点的人数总和(每个点都代表一部分人数)就是支持这个团体i中每个人的总得票数。记录每个出度为0的点的得票数,取最大值。最后对于新图中的每个点当其的得票数为最大值时,其强连通分量团体中的所有点都能够当选。
注意:从样例中可以得到一个人可以同时支持多个人,且支持的人都能得到他的全部票数
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 #include<vector> 5 #include<queue> 6 #include<set> 7 using namespace std; 8 const int maxn=20010; 9 const int maxm=50010; 10 struct edge{ 11 int to,nxt; 12 }edge[maxm]; 13 int head[maxn],tot; 14 int low[maxn],dfn[maxn],stack[maxn],belong[maxn],cnt[maxn]; 15 int index,top; 16 int scc; 17 bool vis[maxn],vis_[maxn]; 18 int num[maxn],in[maxn],out[maxn]; 19 vector<int>G[maxn]; 20 21 void addedge(int u,int v) 22 { 23 edge[tot].to=v; 24 edge[tot].nxt=head[u]; 25 head[u]=tot++; 26 } 27 28 void addedge_(int u,int v) 29 { 30 G[u].push_back(v); 31 } 32 33 void tarjan(int u) 34 { 35 int v; 36 low[u]=dfn[u]=++index; 37 stack[top++]=u; 38 vis[u]=true; 39 for ( int i=head[u];i!=-1;i=edge[i].nxt ) { 40 v=edge[i].to; 41 if ( !dfn[v] ) { 42 tarjan(v); 43 low[u]=min(low[u],low[v]); 44 } 45 else if ( vis[v] ) low[u]=min(low[u],dfn[v]); 46 } 47 if ( low[u]==dfn[u] ) { 48 scc++; 49 do { 50 v=stack[--top]; 51 vis[v]=false; 52 belong[v]=scc; 53 num[scc]++; 54 } 55 while ( v!=u ); 56 } 57 } 58 59 void solve(int N) 60 { 61 memset(dfn,0,sizeof(dfn)); 62 memset(vis,false,sizeof(vis)); 63 memset(num,0,sizeof(num)); 64 index=scc=top=0; 65 for ( int i=1;i<=N;i++ ) { 66 if ( !dfn[i] ) tarjan(i); 67 } 68 } 69 70 void init() 71 { 72 tot=0; 73 memset(head,-1,sizeof(head)); 74 } 75 76 int BFS(int u) 77 { 78 memset(vis,false,sizeof(vis)); 79 queue<int>que; 80 que.push(u); 81 vis[u]=true; 82 int now=num[u]; 83 while ( !que.empty() ) { 84 int v=que.front(); 85 que.pop(); 86 for ( int i=0;i<G[v].size();i++ ) { 87 int j=G[v][i]; 88 if ( !vis[j] ) { 89 vis[j]=true; 90 que.push(j); 91 now+=num[j]; 92 } 93 } 94 } 95 return now; 96 } 97 98 int main() 99 { 100 int T,i,j,k,h,x,y,z,n,m,ans,now,cnt_; 101 scanf("%d",&T); 102 for ( h=1;h<=T;h++ ) { 103 scanf("%d%d",&n,&m); 104 init(); 105 for ( i=1;i<=n;i++ ) G[i].clear(); 106 for ( i=1;i<=m;i++ ) { 107 scanf("%d%d",&x,&y); 108 x++;y++; 109 addedge(x,y); 110 } 111 solve(n); 112 memset(in,0,sizeof(in)); 113 memset(out,0,sizeof(out)); 114 memset(cnt,0,sizeof(cnt)); 115 memset(vis_,false,sizeof(vis_)); 116 for ( i=1;i<=n;i++ ) { 117 for ( j=head[i];j!=-1;j=edge[j].nxt ) { 118 int v=edge[j].to; 119 if ( belong[v]!=belong[i] ) { 120 in[belong[v]]++; 121 out[belong[i]]++; 122 addedge_(belong[v],belong[i]); 123 } 124 } 125 } 126 ans=0; 127 k=0; 128 for ( i=1;i<=scc;i++ ) { 129 if ( out[i]==0 ) { 130 cnt[i]=BFS(i); 131 if ( cnt[i]>ans ) ans=cnt[i]; 132 } 133 } 134 for ( i=1;i<=scc;i++ ) { 135 if ( cnt[i]==ans ) { 136 vis_[i]=true; 137 k+=num[i]; 138 } 139 } 140 printf("Case %d: %d\n",h,ans-1); 141 cnt_=0; 142 for ( i=1;i<=n;i++ ) { 143 if ( vis_[belong[i]] ) { 144 printf("%d",i-1); 145 if ( ++cnt_==k ) { 146 printf("\n"); 147 break; 148 } 149 else printf(" "); 150 } 151 } 152 } 153 return 0; 154 }
5.(HDOJ3072)http://acm.hdu.edu.cn/showproblem.php?pid=3072
题意:
分析:tarjan+类最小生成树
6.(HDOJ3861)http://acm.hdu.edu.cn/showproblem.php?pid=3861
题意:有一个国王,有n个城市和m条路径。先将国王分成几个地区,地区满足:若城市u能到城市v同时城市v能到城市u,则它们必定属于一个地区。于此同时还有其他点也可以属于该地区,即一个地区的两点u,v一定存在一条不经过其他地区的从u到v或者从v到u的线路。
分析:tarjan+最小路径覆盖。先用tarjan进行缩点,在新图的基础上构建二分图匹配求出最大匹配进而得到最小路径数。注意点数较多,所有二分匹配用邻接表而不是临界矩阵存。同时点数好像比所给的范围要大的多
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 #include<vector> 5 using namespace std; 6 const int maxn=20010; 7 const int maxm=100010; 8 struct edge{ 9 int to,nxt,val; 10 }edge[maxm]; 11 int head[maxn],tot; 12 int low[maxn],dfn[maxn],stack[maxn],belong[maxn],girl[maxn]; 13 int index,top; 14 int scc,n; 15 bool vis[maxn]; 16 vector<int>G[maxn]; 17 18 void addedge(int u,int v) 19 { 20 edge[tot].to=v; 21 edge[tot].nxt=head[u]; 22 head[u]=tot++; 23 } 24 25 void tarjan(int u) 26 { 27 int v; 28 low[u]=dfn[u]=++index; 29 stack[top++]=u; 30 vis[u]=true; 31 for ( int i=head[u];i!=-1;i=edge[i].nxt ) { 32 v=edge[i].to; 33 if ( !dfn[v] ) { 34 tarjan(v); 35 low[u]=min(low[u],low[v]); 36 } 37 else if ( vis[v] ) low[u]=min(low[u],dfn[v]); 38 } 39 if ( low[u]==dfn[u] ) { 40 scc++; 41 do { 42 v=stack[--top]; 43 vis[v]=false; 44 belong[v]=scc; 45 } 46 while ( v!=u ); 47 } 48 } 49 50 void solve() 51 { 52 memset(dfn,0,sizeof(dfn)); 53 memset(vis,false,sizeof(vis)); 54 index=scc=top=0; 55 for ( int i=1;i<=n;i++ ) { 56 if ( !dfn[i] ) tarjan(i); 57 } 58 } 59 60 void init() 61 { 62 tot=0; 63 memset(head,-1,sizeof(head)); 64 } 65 66 bool find(int x) 67 { 68 int i,j; 69 for ( i=0;i<G[x].size();i++ ) { 70 j=G[x][i]; 71 if ( vis[j]==false ) { 72 vis[j]=true; 73 if ( girl[j]==0 || find(girl[j]) ) { 74 girl[j]=x; 75 return true; 76 } 77 } 78 } 79 return false; 80 } 81 82 int main() 83 { 84 int m,i,j,k,x,y,z,ans,T; 85 scanf("%d",&T); 86 while ( T-- ) { 87 scanf("%d%d",&n,&m); 88 init(); 89 while ( m-- ) { 90 scanf("%d%d",&x,&y); 91 addedge(x,y); 92 } 93 solve(); 94 for ( i=1;i<=scc;i++ ) G[i].clear(); 95 for ( i=1;i<=n;i++ ) { 96 for ( j=head[i];j!=-1;j=edge[j].nxt ) { 97 int v=edge[j].to; 98 x=belong[i]; 99 y=belong[v]; 100 if ( x!=y ) G[x].push_back(y); 101 } 102 } 103 ans=0; 104 memset(girl,0,sizeof(girl)); 105 for ( i=1;i<=scc;i++ ) { 106 memset(vis,false,sizeof(vis)); 107 if ( find(i) ) ans++; 108 } 109 ans=scc-ans; 110 printf("%d\n",ans); 111 } 112 return 0; 113 }
7.(HDOJ2242)http://acm.hdu.edu.cn/showproblem.php?pid=2242
以上是关于专题训练之强连通分量和2-sat的主要内容,如果未能解决你的问题,请参考以下文章