zoukankan      html  css  js  c++  java
  • 强连通分量

    tarjan

    基于深度优先搜索,用于寻找有向图中的连通块。
    主要代码如下:

    inline void tarjan(int x){
    	dfn[x]=low[x]=++idx;
    	vis[x]=1;
    	sta[++top]=x;
    	for(int i=head[x];i;i=e[i].nxt){
    		int v=e[i].v;
    		if(!dfn[v]){//还未访问过这个节点
    			tarjan(v);
    			low[x]=min(low[x],low[v]);
    		}
    		else if(vis[v]==1){//这个节点在栈中
    			low[x]=min(low[x],dfn[v]);
    		}
    	}
    	if(dfn[x]==low[x]){//找到一个强联通分量
    		++cnt;
    		while(x!=sta[top]){
    			vis[sta[top]]=0;
    			qlt[sta[top]]=cnt;//染色,标记这个节点属于第几号强联通分量
    			siz[cnt]++;//记录这一个连通块的节点数
    			top--;
    		}
    		vis[x]=0;//还要将这个节点弹出来
    		qlt[x]=cnt;
    		siz[cnt]++;
    		top--;
    	}
    }
    

    如果是要遍历整张图找到所有的连通块,通常还要加这句

    for(int i=1;i<=n;i++){
    	if(!dfn[i]) tarjan(i);
    }
    

    因为图中不一定全部都是联通的。
    每一个点只访问了一次,所以tarjan的时间复杂度是O(n)的。
    通常还要用到tarjan缩点。缩点的时候将全部边遍历一次,
    如果两个节点不属于同一个连通块,那么就连一条边,为了避免这种情况:
    a->c b->c a,b属于同一个连通块。
    可以用并查集处理一下,避免重复将两个相同的连通块连边。(重复连边好像也并没有什么影响)

    题目


    USACO 2003 Fall:
    popular cow
    这道题首先要知道:对于一个有向无环图,出度为零的点,从其他任意一个点都可以到达这个点。
    所以我们只需要将原图用tarjan缩点变成一个有向无环图,找到出度为零的点,输出那个连通块的大小就行了。

    #include<bits/stdc++.h>
    #define cg c=getchar()
    #define N 10020
    #define M 50050
    using namespace std;
    template<typename xxx>inline void read(xxx &x){
    	x=0;int f=1;char cg;
    	while(c<'0'||c>'9'){if(c=='-')f=-1;cg;}
    	while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);cg;}
    	x*=f;
    }
    template<typename xxx>inline void print(xxx x){
    	if(x<0) x=-x,putchar('-');
    	if(x>9) print(x/10);
    	putchar(x%10+48);
    }
    int n,m,aa,bb;
    struct node{
    	int v,nxt;
    }e[M];
    int head[N];
    int tot;
    inline void add(int a,int b){
    	++tot;
    	e[tot].v=b;
    	e[tot].nxt=head[a];
    	head[a]=tot;
    }
    int idx;
    int dfn[N];
    int low[N];
    int sta[N];
    int vis[N];
    int top;
    int cnt;
    int qlt[N];
    int siz[N];
    int cd[N];
    inline void tarjan(int x){
    	dfn[x]=low[x]=++idx;
    	vis[x]=1;
    	sta[++top]=x;
    	for(int i=head[x];i;i=e[i].nxt){
    		int v=e[i].v;
    		if(!dfn[v]){
    			tarjan(v);
    			low[x]=min(low[x],low[v]);
    		}
    		else if(vis[v]==1){
    			low[x]=min(low[x],dfn[v]);
    		}
    	}
    	if(dfn[x]==low[x]){
    		++cnt;
    		while(x!=sta[top]){
    			vis[sta[top]]=0;
    			qlt[sta[top]]=cnt;
    			siz[cnt]++;
    			top--;
    		}
    		vis[x]=0;
    		qlt[x]=cnt;
    		siz[cnt]++;
    		top--;
    	}
    }
    int main(){
    	read(n);read(m);
    	for(int i=1;i<=m;i++){
    		read(aa);read(bb);
    		add(aa,bb);
    	}
    	for(int i=1;i<=n;i++){
    		if(!dfn[i]) tarjan(i);
    	}
    	//进行缩点
    	for(int i=1;i<=n;i++){
    		for(int j=head[i];j;j=e[j].nxt){
    			int v=e[j].v;
    			if(qlt[i]!=qlt[v]){
    				cd[qlt[i]]++;//因为不用重建一张图,只需要记录出度就行了
    			}
    		}
    	}
    	//因为重构的图也有可能存在两个连通块之间没有边连接,它们的出度也为零,所以要考虑这种情况
    	int ans=0;
    	int id;
    	for(int i=1;i<=cnt;i++){
    		if(cd[i]==0){
    			ans++;
    			id=i;
    		}
    	}
    	if(ans==1){//只能有一个出度为零的连通块
    		print(siz[id]);
    	}
    	else print(0);
    }
    

    IOI 1996 网络协议
    这道题有两个问题,第一个问题还是比较明显,就是缩点后看有几个入度为0的点,每一个入度为零的点都必须放消息。
    第二问就相当于将缩点后的图变成一个连通块。每一个入度为零的边都要增加一条入度,每一个出度为零的边都要增加一条出度。最后取较大值。
    有个小细节,如果缩点后只有一个点,那么就不用加边了,所以要特判一下。
    code:

    #include<bits/stdc++.h>
    #define N 120
    #define M 13000
    #define cg c=getchar()
    using namespace std;
    template<typename xxx>inline void read(xxx &x){
    	x=0;int f=1;char cg;
    	while(c<'0'||c>'9'){if(c=='-')f=-1;cg;}
    	while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);cg;}
    	x*=f;
    } 
    template<typename xxx>inline void print(xxx x){
    	if(x<0) x=-x,putchar('-');
    	if(x>9) print(x/10);
    	putchar(x%10+48);
    }
    struct node{
    	int u,v,nxt;
    }e[M],e2[M];
    int head[N];
    int tot;
    inline void add(int a,int b){
    	++tot;
    	e[tot].u=a;
    	e[tot].v=b;
    	e[tot].nxt=head[a];
    	head[a]=tot;
    }
    int n;
    int aa;
    int idx;
    int top;
    int dfn[N];
    int low[N];
    int vis[N];
    int sta[N];
    int qlt[N];
    int cnt;
    inline void tarjan(int x){
    	dfn[x]=low[x]=++idx;
    	vis[x]=1;
    	sta[++top]=x;
    	for(int i=head[x];i;i=e[i].nxt){
    		int v=e[i].v;
    		if(!dfn[v]) {
    			tarjan(v);
    			low[x]=min(low[x],low[v]);
    		}
    		else if(vis[v]==1){
    			low[x]=min(low[x],dfn[v]);
    		}
    	}
    	if(dfn[x]==low[x]){
    		++cnt;
    		while(x!=sta[top]){
    			qlt[sta[top]]=cnt;
    			vis[sta[top]]=0;
    			top--;
    		}
    		qlt[x]=cnt;
    		vis[x]=0;
    		top--;
    	}
    } 
    int rd[N];
    int cd[N];
    int ans;
    int main(){
    //	freopen("protocols10.in","r",stdin);
    	read(n);
    	for(int i=1;i<=n;i++){
    		while(true){
    			read(aa);
    			if(aa==0) break;
    			add(i,aa);
    		}
    	}
    	for(int i=1;i<=n;i++){
    		if(!dfn[i]) tarjan(i);
    	}
    	for(int i=1;i<=tot;i++){
    		int x=e[i].u;
    		int y=e[i].v;
    		if(qlt[x]!=qlt[y]) {
    			rd[qlt[y]]++;
    			cd[qlt[x]]++;
    		}
    	}
    	int ans1=0;
    	int ans2=0;
    	for(int i=1;i<=cnt;i++){
    		if(rd[i]==0){
    			ans1++;
    			ans++;
    		}
    		if(cd[i]==0){
    			ans2++;
    		} 
    	}
    	print(ans);putchar('
    ');
    	if(cnt==1) print(0);//特判 只有一个连通块,不需要加边。
    	else print(max(ans1,ans2));
    }
    

    消息的传递:
    跟上一题的第一问一样。。。只不过输入变成了邻接矩阵。
    code:

    #include<bits/stdc++.h>
    #define cg c=getchar()
    #define N 1200
    #define M N*(N-1)/2
    using namespace std;
    template<typename xxx>inline void read(xxx &x){
    	x=0;int f=1;char cg;
    	while(c<'0'||c>'9'){if(c=='-')f=-1;cg;}
    	while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);cg;}
    	x*=f;
    } 
    template<typename xxx>inline void print(xxx x){
    	if(x<0) x=-x,putchar('-');
    	if(x>9) print(x/10);
    	putchar(x%10+48);
    }
    struct node{
    	int u,v,nxt;
    }e[M];
    int head[N];
    int tot;
    inline void add(int a,int b){
    	++tot;
    	e[tot].u=a;
    	e[tot].v=b;
    	e[tot].nxt=head[a];
    	head[a]=tot;
    } 
    int n;
    int aa;
    /*--------------------------*/
    int idx;
    int dfn[N];
    int low[N];
    int vis[N];
    int sta[N];
    int top;
    int cnt;
    int qlt[N];
    inline void tarjan(int x){
    	dfn[x]=low[x]=++idx;
    	sta[++top]=x;
    	vis[x]=1;
    	for(int i=head[x];i;i=e[i].nxt){
    		int v=e[i].v;
    		if(!dfn[v]){
    			tarjan(v);
    			low[x]=min(low[x],low[v]);
    		}
    		else if(vis[v]==1){
    			low[x]=min(low[x],dfn[v]);
    		}
    	}
    	if(dfn[x]==low[x]){
    		++cnt;
    		while(x!=sta[top]){
    			qlt[sta[top]]=cnt;
    			vis[sta[top]]=0;
    			top--;
    		}
    		qlt[x]=cnt;
    		vis[x]=0;
    		top--;
    	}
    }
    int rd[N];
    int main(){
    	read(n);
    	for(int i=1;i<=n;i++){
    		for(int j=1;j<=n;j++){
    			read(aa);
    			if(aa==1){
    				add(i,j);
    			}
    		}
    	}
    	for(int i=1;i<=n;i++){
    		if(!dfn[i]) tarjan(i);
    	}
    	for(int i=1;i<=tot;i++){
    		int x=e[i].u;
    		int y=e[i].v;
    		if(qlt[x]!=qlt[y]){
    			rd[qlt[y]]++;
    		}
    	}
    	int ans=0;
    	for(int i=1;i<=cnt;i++){
    		if(rd[i]==0){
    			ans++;
    		}
    	}
    	print(ans);
    }
    

    间谍网络:
    这道题跟之前几道差别不大,缩点统计入度就AC了。(我WA了十几次)
    code:

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    template<class T>
    inline void Read(T &x)
    {
    	x=0; int s=1; char ch=getchar();
    	while(ch<'0' || ch>'9') {if(ch=='-') s=-1; ch=getchar();}
    	while(ch>='0' && ch<='9'){x=x*10+ch-'0'; ch=getchar();} x*=s;
    }
    template<class T>
    void print(T x)
    {
    	if(x==0) return;
    	print(x/10);
    	putchar(x%10+'0');
    }
    struct node{
    	int to,next;
    };
    const int N=3e4+10;
    node edge[N<<1];
    int len,h[N];
    int n,p,val[N],r,in[N],out[N];
    int indx,low[N],dfn[N],s[N],top,f[N],cnt,val2[N];
    bool visited[N];
    void add(int x,int y)
    {
    	edge[++len].to=y; edge[len].next=h[x];
    	h[x]=len;
    }
    void tarjan(int u)
    {
    	dfn[u]=low[u]=++indx; s[++top]=u; visited[u]=true;
    	
    	for(int i=h[u]; i; i=edge[i].next)
    	{
    		int to=edge[i].to;
    		
    		if(!dfn[to])
    		{
    			tarjan(to);
    			low[u]=min(low[to],low[u]);
    		}
    		else if(visited[to]) low[u]=min(dfn[to],low[u]);
    	}
    	
    	if(dfn[u]==low[u])
    	{
    		int v=-1; cnt++;
    		
    		do{
    			v=s[top--]; f[v]=cnt; val2[cnt]=min(val2[cnt],val[v]);
    			visited[v]=false;
    		}while(u!=v);
    	}
    }
    void init()
    {
    	memset(val,0x3f,sizeof(val));
    	memset(val2,0x3f,sizeof(val2));
    	scanf("%d%d",&n,&p);
    	for(int i=1; i<=p; i++)
    	{
    		int id,money; Read(id); Read(money);
    		val[id]=money;
    	}
    	Read(r);
    	for(int i=1; i<=r; i++){
    		int x,y; Read(x); Read(y);
    		add(x,y); in[y]++; out[x]++;
    	}
    	bool flag=false;
    	for(int i=1; i<=n; i++)
    	if(val[i]!=0x3f3f3f3f && out[i]!=0) flag=true;
    	
    	if(!flag)
    	{
    		printf("NO
    %d
    ",1); return;
    	}
    	for(int i=1; i<=n; i++){
    		if(!in[i] && val[i]==0x3f3f3f3f){
    			puts("NO");
    			printf("%d
    ",i);
    			exit(0);
    		}
    	}
    	memset(in,0,sizeof(in));
    	for(int i=1; i<=n; i++)
    	if(!dfn[i]) tarjan(i);
    	for(int u=1; u<=n; u++)
    	for(int i=h[u]; i; i=edge[i].next){
    		int to=edge[i].to;
    		if(f[u]!=f[to]) in[f[to]]++;
    	}
    	int ans=0;
    	for(int i=1; i<=cnt; i++)
    	if(!in[i]) ans+=val2[i];
    	printf("YES
    %d",ans);
    }
    int main(){
    	init();
    	return 0;
    }
    

    APIO 2009 抢掠计划
    这道题还是比较好想,先tarjan缩点后新建一张图跑最长路就行了。(不知道大佬有没有其他更简单的做法)
    code:

    #include<bits/stdc++.h>
    #define N 500004
    #define inf 0x3f3f3f3f
    #define cg c=getchar()
    using namespace std;
    template<typename xxx>inline void read(xxx &x){
    	x=0;int f=1;char cg;
    	while(c<'0'||c>'9'){if(c=='-')f=-1;cg;}
    	while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);cg;}
    	x*=f;
    }
    template<typename xxx>inline void print(xxx x){
    	if(x<0) x=-x,putchar('-');
    	if(x>9) print(x/10);
    	putchar(x%10+48);
    }
    struct node{
    	int u,v,nxt;
    }e[N],e2[N];
    int head[N];
    int tot;
    inline void add(int a,int b){
    	++tot;
    	e[tot].v=b;
    	e[tot].u=a;
    	e[tot].nxt=head[a];
    	head[a]=tot;
    }
    int n;
    int m;
    int aa,bb;
    int s,p;
    int val[N];
    int w[N];
    int pub[N];
    int jg[N];
    /*-------------------------------*/
    int idx;
    int dfn[N];
    int low[N];
    int st[N];
    int top;
    int vis[N];
    int cnt;
    int qlt[N];
    inline void tarjan(int x){
    	dfn[x]=low[x]=++idx;
    	st[++top]=x;
    	vis[x]=1;
    	for(int i=head[x];i;i=e[i].nxt){
    		int v=e[i].v;
    		if(!dfn[v]){
    			tarjan(v);
    			low[x]=min(low[x],low[v]);
    		}
    		else if(vis[v]==1){
    			low[x]=min(low[x],dfn[v]);
    		}
    	}
    	if(dfn[x]==low[x]){
    		++cnt;
    		while(x!=st[top]){
    			qlt[st[top]]=cnt;
    			vis[st[top]]=0;
    			w[cnt]+=val[st[top]];
    			if(pub[st[top]]==1) jg[cnt]=1;
    			top--;
    		}
    		qlt[x]=cnt;
    		vis[x]=0;
    		w[cnt]+=val[x];
    		if(pub[x]==1) jg[cnt]=1;
    		top--;
    	}
    }
    /*---------------------------*/
    int head2[N];
    int tot2;
    inline void add2(int a,int b){
    	++tot2;
    	e2[tot2].u=a;
    	e2[tot2].v=b;
    	e2[tot2].nxt=head2[a];
    	head2[a]=tot2;
    }
    /*-0------------------------------*/
    int dis[N];
    queue<int>q;
    inline void spfa(){
    	for(int i=1;i<=cnt;i++){
    		dis[i]=0;vis[i]=0;
    	}
    	s=qlt[s];
    	q.push(s);vis[s]=1;dis[s]=w[s];
    	while(!q.empty()){
    		int x=q.front();
    		q.pop();
    		vis[x]=0;
    		for(int i=head2[x];i;i=e2[i].nxt){
    			int v=e2[i].v;
    			if(dis[v]<dis[x]+w[v]){
    				dis[v]=dis[x]+w[v];
    				if(!vis[v]){
    					vis[v]=1;
    					q.push(v);
    				}
    			}
    		}
    	}
    }
    int main(){
    	read(n);read(m);
    	for(int i=1;i<=m;i++){
    		read(aa);read(bb);
    		add(aa,bb);
    	}
    	for(int i=1;i<=n;i++) read(val[i]);
    	read(s);read(p);
    	for(int i=1;i<=p;i++){
    		read(aa);
    		pub[aa]=1;
    	}
    	for(int i=1;i<=n;i++){
    		if(!dfn[i]) tarjan(i);
    	}
    	for(int i=1;i<=m;i++){
    		int x=e[i].u;
    		int y=e[i].v;
    		if(qlt[x]!=qlt[y]){
    			add2(qlt[x],qlt[y]);
    		}
    	}
    	spfa();
    	int ans=-inf;
    	for(int i=1;i<=cnt;i++){
    		if(jg[i]==1){
    			ans=max(ans,dis[i]);
    		}
    	}
    	print(ans);
    }
    

    POI 2001 和平委员会

    2-sat问题,推荐博客:
    https://blog.csdn.net/JarjingX/article/details/8521690
    https://blog.sengxian.com/algorithms/2-sat

    code:

    #include<bits/stdc++.h>
    #define cg c=getchar()
    #define N 30000
    #define M 30000
    using namespace std;
    template<typename xxx>inline void read(xxx &x){
    	x=0;int f=1;char cg;
    	while(c<'0'||c>'9'){if(c=='-')f=-1;cg;}
    	while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);cg;}
    	x*=f;
    } 
    template<typename xxx>inline void print(xxx x){
    	if(x<0) x=-x,putchar('-');
    	if(x>9) print(x/10);
    	putchar(x%10+48);
    }
    vector<int>e[N];
    int n,m;
    int aa,bb;
    inline int other(int x){
    	if(x%2==0) return x-1;
    	return x+1; 
    }
    int idx;
    int dfn[N];
    int low[N];
    int st[N];
    int top;
    int cnt;
    int qlt[N];
    int vis[N];
    inline void tarjan(int x){
    	dfn[x]=low[x]=++idx;
    	st[++top]=x;
    	vis[x]=1;
    	for(int i=0;i<e[x].size();i++){
    		int v=e[x][i];
    		if(!dfn[v]) {
    			tarjan(v);
    			low[x]=min(low[x],low[v]);
    		}
    		else if(vis[v]==1){
    			low[x]=min(low[x],dfn[v]);
    		}
    	}
    	if(dfn[x]==low[x]){
    		++cnt;
    		while(x!=st[top]){
    			qlt[st[top]]=cnt;
    			vis[st[top]]=0;
    			top--;
    		}
    		qlt[x]=cnt;
    		vis[x]=0;
    		top--;
    	}
    }
    int main(){
    	read(n);read(m);
    	for(int i=1;i<=m;i++){
    		read(aa);read(bb);
    		e[aa].push_back(other(bb));
    		e[bb].push_back(other(aa));
    	}
    	for(int i=1;i<=(n<<1);i++){
    		if(!dfn[i]) tarjan(i);
    	}
    	for(int i=1;i<=n;i++){
    		int x=2*i-1;
    		int y=2*i;
    		if(qlt[x]==qlt[y]){
    			printf("NIE");
    			return 0;
    		}
    	}
    	for(int i=1;i<=n;i++){
    		int x=2*i-1;
    		int y=2*i;
    		if(qlt[x]<qlt[y]){
    			print(x);putchar(' ');
    		}
    		else{
    			print(y);putchar(' ');
    		}
    	}
    }
    

    心得

    • 有向无环图中一个出度为零的点,从任意一个点出发都可到达它。
    • 有向无环图至少有一个入度为零的点。
    • 有向无环图的生成树个数等于入度非零的节点的入度积。
    • 强联通分量的题好像多数与入度出度有关,想题的时候可以往这方面考虑。
  • 相关阅读:
    106. Construct Binary Tree from Inorder and Postorder Traversal
    105. Construct Binary Tree from Preorder and Inorder Traversal
    449. Serialize and Deserialize BST
    114. Flatten Binary Tree to Linked List
    199. Binary Tree Right Side View
    173. Binary Search Tree Iterator
    98. Validate Binary Search Tree
    965. Univalued Binary Tree
    589. N-ary Tree Preorder Traversal
    eclipse设置总结
  • 原文地址:https://www.cnblogs.com/doublety/p/11519454.html
Copyright © 2011-2022 走看看