强连通分量题目

复习:

有向图的连通性

强连通:如果两个点:u,v是互相达到的

无向图:联通   有向图:强连通

图中有多少SCC:暴力O(V^2+E)

kosaraju算法O(V+E):反图

(1)有向图G,建立反图rG,不会改变连通性

(2)对原图G做DFS,标记点的先后顺序,递归在最底层的点标记最小,回退过程中,其他点的优先级加大

(3)在反图RG上做DFS,从优先级大的开始做

Tarjan算法O(V+E):用栈分离不同的SCC

最先入栈的点:是这个SCC的祖先,他的num[]=low[]

比上面要快些

#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<cstdio>
#include<queue>
#include<map>
#include<vector>
#include<set>
using namespace std;
const int maxn=10010;
const int INF=0x3fffffff;
typedef long long LL;
int cnt=0,dfn=0;
int low[maxn],num[maxn];
int sccno[maxn],stack[maxn],top;
//这个就表明属于哪个SCC
vector<int> g[maxn];
void dfs(int u){
	stack[top++]=u;
	low[u]=num[u]=++dfn;
	for(int i=0;i<g[u].size();i++){
		int v=g[u][i];
		if(!num[v]){
			dfs(v);
			low[u]=min(low[u],low[v]);
		}
		else if(!sccno[v]){  //处理回退边 
 			low[u]=min(low[u],num[v]);
		}
	}
	if(low[u]==num[u]){  //栈底的点是SCC的祖先, 
		cnt++;
		while(1){
			int v=stack[top--];
			sccno[v]=cnt;
			if(u==v) break; //到达了祖先 
		}
	}
 
}
 void tarjan(int n){
 	cnt=top=dfn=0;
 	memset(sccno,0,sizeof(sccno));
 	memset(num,0,sizeof(num));
 	memset(low,0,sizeof(low));
 	for(int i=1;i<=n;i++){
 		if(!num[i]) dfs(i);
	 }
 } 
 
int main(){
	int n,m,u,v;
	while(scanf("%d %d",&n,&m),n!=0||m!=0){
		for(int i=0;i<=n;i++){
			g[i].clear();
		}
		for(int i=0;i<m;i++){
			scanf("%d %d",&u,&v);
			g[u].push_back(v);
		}
		tarjan(n);
		if(cnt==1) printf("Yes\n");
		else printf("No\n");
	} 
return 0;
}

  

1513:【 例 1】受欢迎的牛

感觉很多题里面在缩点之后,是需要计算度的。

把每一个极大联通子图看成一个点(缩点),但后再组成一个图,这个图是没有环的在这个图中,至少存在1个出度为0的点,如果出度为0的点大于1,那么是无法确定的,但是如果出度为1的点只有1个,而且其他的点都能到达他的话,就可以确定答案就是那个点里面点数目 

#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<stack>
#include<cstdio>
#include<queue>
#include<map>
#include<vector>
#include<set>
using namespace std;
const int maxn=10050;
const int INF=0x3fffffff;
typedef long long LL;
//有向图:极大连通子图
//把每一个极大联通子图看成一个点(缩点),但后再组成一个图,这个图是没有环的
//在这个图中,至少存在1个出度为0的点,如果出度为0的点大于1,那么是无法确定的
//但是如果出度为1的点只有1个,而且其他的点都能到达他的话,就可以确定
//答案就是那个点里面点数目 
struct node{
	int to,next;
}ed[maxn*20];
int head[maxn],num[maxn],low[maxn];
int id[maxn],col[maxn];
//分别是每个点所属的极大联通分量,然后是这个连通分量的数量 
int deg[maxn];   //这个极大联通子图的出度 
bool inst[maxn];  //在不在栈里面
stack<int> st;
int cnt,tot,nu,dfn;
void add(int x,int y){
	ed[++nu].to=y;
	ed[nu].next=head[x];
	head[x]=nu;
} 
void tarjin(int u){
	num[u]=low[u]=++dfn;
	inst[u]=1;
	st.push(u);
	for(int i=head[u];i;i=ed[i].next){
		int v=ed[i].to;
		if(!num[v]){
			tarjin(v);
			low[u]=min(low[u],low[v]);
		}
		else if(inst[v]){ //有回退边
			low[u]=min(low[u],num[v]);
		}
	}
	int k;
	if(low[u]==num[u]){
		cnt++;
		do{
			k=st.top();
			st.pop();
			inst[k]=0;    //这里需要改变
			id[k]=cnt;
			col[cnt]++;
		}while(k!=u);
	}
}

int main(){
	int n,m;
	scanf("%d %d",&n,&m);
	for(int i=0;i<m;i++){
		int x,y;
		scanf("%d %d",&x,&y);
		add(x,y);
	}
	for(int i=1;i<=n;i++){
		if(!num[i]) tarjin(i);
	}
	for(int i=1;i<=n;i++){
		for(int j=head[i];j;j=ed[j].next){
			int u=ed[j].to;
			if(id[i]!=id[u]) deg[id[i]]++; //i这个极大连通子图的出度+1 
		}
	}
	for(int i=1;i<=cnt;i++){
		if(!deg[i]){
			if(tot) {
				printf("0\n");break;  //因为只能有一个
			}
			tot=i;
		}
	}
	printf("%d\n",col[tot]);
	
return 0;
}

1514:【例 2】最大半连通子图

//求极大半连通子图 极大半连通子图 极大半连通子图 极大半连通子图 极大半连通子图 极大半连通子图 
// 此题题意就是:缩点后,求一条含有最多点数的DAG链,(这就是题中的最大半联通子图),并求出有多少个不同的最长链
//主要有三个部分:1.缩点;2.DAG记忆化;3.离散化去重;(难点)4.链式前向星。
///思路:
//因为存在环,所以先缩点,缩完点以后,此时图就变成DAG,然后进行一遍dfs扫描,dfs时注意用记忆化,然后边扫边记录得到求最大值数组和求个数数组,最后统计答案即可。
//注意点:
//1.在求个数的时候要时刻取模,取模不能停!2.因为要求种类个数,所以要保证不重边(最大值与重边无关,但种类与重边有关)所以需要离散化

#include <bits/stdc++.h>
using namespace std;
const int N=1e5+5,M=1e6+5;

int n,m,MOD,cnt,ans,MAX,k,now1,now2;
int head[N],u[M],v[M];
//记录输入的u[]v[]是便于后面统计
int col,now,top,dfn[N],low[N],color[N],sta[N],si[N];
int rd[N],cd[N],dep[N],sum[N];
bool f[N];
struct edge{int next,from,to;}e[M];
struct node{int u,v;}C[M],b[M];

inline bool cmp(node a,node b){return (a.u<b.u || a.u==b.u && a.v<b.v|| a.u==b.u && a.v==b.v);} //排序,因为要离散化,去重
inline void add(int u,int v){cnt++;e[cnt].next=head[u];e[cnt].to=v;head[u]=cnt;}

inline void tarjan(int u)
{
    dfn[u]=low[u]=++now;
    sta[++top]=u;   
    for (register int i=head[u]; i; i=e[i].next)
    {
        if (!dfn[e[i].to])
        {
            tarjan(e[i].to);
            low[u]=min(low[u],low[e[i].to]);
        }
        else if (!color[e[i].to])   //还没有确定是哪个连通块,也就是代替了vis[]数组的作用
        low[u]=min(low[u],dfn[e[i].to]);
    }
    if (low[u]==dfn[u])
    {
        color[u]=++col;
        si[col]++;
        while (sta[top]!=u) color[sta[top]]=col,si[col]++,top--;
        top--;
    }
}

inline void dfs(int u,int fa)
{
    f[u]=true;
    if (!cd[u]) {dep[u]=si[u]; sum[u]=1; return;}  //出度为0,到了递归边界,开始会退,计算
//看出来了吗,这其实是回溯
//dep[]数组就是这条链能够拥有的节点数,sum[]数组是方案数
    for (register int i=head[u]; i; i=e[i].next)
    if (e[i].to!=fa)
    {
        if (!f[e[i].to]) dfs(e[i].to,u);
        if (dep[e[i].to]+si[u]>dep[u]) dep[u]=dep[e[i].to]+si[u],sum[u]=sum[e[i].to]%MOD;
        else if (dep[e[i].to]+si[u]==dep[u]) sum[u]=(sum[u]+sum[e[i].to])%MOD;
    }
}

int main(){
memset(head,0,sizeof(head));
memset(dfn,0,sizeof(dfn));
    scanf("%d%d%d",&n,&m,&MOD);
    for (register int i=1; i<=m; ++i) scanf("%d%d",&u[i],&v[i]),add(u[i],v[i]);

//缩点    
    for (register int i=1; i<=n; ++i) if (!dfn[i]) tarjan(i);

//离散化去重
for (register int i=1; i<=m; ++i) 
if (color[u[i]]!=color[v[i]]) k++,C[k].u=color[u[i]],C[k].v=color[v[i]]; //新边,但是还没有去重,之所以要这个是因为还需要排序
sort(C+1,C+k+1,cmp);
//在这里排序
cnt=0; 
cnt++;
b[cnt].u=C[1].u,b[cnt].v=C[1].v;
now1=b[cnt].u,now2=b[cnt].v;
for (register int i=2; i<=k; ++i) if (C[i].u!=now1|| C[i].v!=now2) 
cnt++,b[cnt].u=C[i].u,b[cnt].v=C[i].v,now1=b[cnt].u,now2=b[cnt].v;   //这才是真正的边,没有重复的

//重新建图
    memset(head,0,sizeof(head));
    memset(e,0,sizeof(e));
    for (register int i=1; i<=cnt; ++i) 
    {
    cd[b[i].u]++,rd[b[i].v]++;
    e[i].next=head[b[i].u];
    e[i].from=b[i].u;
    e[i].to=b[i].v;
    head[b[i].u]=i;   //这里
    }

//记忆化搜索
    for (register int i=1; i<=col; ++i) if (!rd[i] && !f[i]) dfs(i,0);   //入度为0,且没有访问过

//统计答案
MAX=0,ans=0;    
    for (register int i=1; i<=col; ++i)
    {
        if (dep[i]>MAX) 
        {   
        MAX=dep[i];
        ans=sum[i];
        }
        else
        if (MAX==dep[i])
        ans=(ans+sum[i])%MOD;
    }   

    printf("%d\n",MAX);
    printf("%d\n",ans);
return 0;
}

1515:网络协议

任务A: 需要求最多在多少学校发送新软件,其实就是求缩点后入度为0的个数(如果入度不为0就可以从其他学校传过来)

任务B:求入度为0的点数与出度为0的点的较大值。因为任务B要求在任意学校投放软件使得所有学校都能收到,所以很明显是需要整张图形成一个环,而环中所有节点入度和出度都不为0,所以需要把所有入度和出度的点度数增加。那么最少的方案就是把出度为0的点连上入度为0的点,所以任务B的答案就是入度为0与出度为0的点的较大值

#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<stack>
#include<cstdio>
#include<queue>
#include<map>
#include<vector>
#include<set>
using namespace std;
const int maxn=1010;
const int INF=0x3fffffff;
typedef long long LL;
/*任务A:
需要求最多在多少学校发送新软件,其实就是求缩点后入度为0的个数(如果入度不为0就可以从其他学校传过来)
任务B:
求入度为0的点数与出度为0的点的较大值。
因为任务B要求在任意学校投放软件使得所有学校都能收到,所以很明显是需要整张图形成一个环,而环中所有节点入度和出度都不为0,所以需要把所有入度和出度的点度数增加。
那么最少的方案就是把出度为0的点连上入度为0的点,所以任务B的答案就是入度为0与出度为0的点的较大值
*/
int n;
struct node{
    int to,next;
}edge[100000];
int head[110],num[110],low[110],vis[110];
int in[110],out[110];
int id[110],sta[110];
int cnt,col;
void link(int a,int b){
    cnt++;
    edge[cnt].to=b;
    edge[cnt].next=head[a];
    head[a]=cnt;
}
int dfn,top;
void tarjin(int u){
    low[u]=num[u]=++dfn;
    sta[top++]=u;
    vis[u]=1;
    for(int i=head[u];i;i=edge[i].next){
        int v=edge[i].to;
        if(!num[v]){
            tarjin(v);
            low[u]=min(low[u],low[v]);
        }
        else if(!id[v]){
            low[u]=min(low[u],num[v]);
        }
    }
    if(low[u]==num[u]){
        col++;
        do{
            top--;
            id[sta[top]]=col;
        }while(sta[top]!=u);
    }
}
int lin[100000][3];
int main(){
    scanf("%d",&n);
    int x,k=0;
    for(int i=1;i<=n;i++){
        scanf("%d",&x);
        while(x!=0){
            link(i,x);
            k++;
            lin[k][1]=i;
            lin[k][2]=x;  //记录下来
            scanf("%d",&x);
        }
    }
    for(int i=1;i<=n;i++){
        if(!num[i]) tarjin(i);
    }
    for(int i=1;i<=k;i++){
        if(id[lin[i][1]]!=id[lin[i][2]]){
            out[id[lin[i][1]]]++;
            in[id[lin[i][2]]]++;
        }
    }
    int ans1=0,ans2=0;
    for(int i=1;i<=col;i++){
        if(in[i]==0) ans1++;
        if(out[i]==0) ans2++;
    }
    if(col==1) cout<<1<<endl<<0;
    else cout<<ans1<<endl<<max(ans2,ans1);
return 0;
}

  

#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<cstdio>
#include<queue>
#include<map>
#include<vector>
#include<set>
using namespace std;
const int maxn=10010;
const int INF=0x3fffffff;
typedef long long LL;
int cnt=0,dfn=0;
int low[maxn],num[maxn];
int sccno[maxn],stack[maxn],top;
//这个就表明属于哪个SCC vector<int> g[maxn]; void dfs(int u){ stack[top++]=u; low[u]=num[u]=++dfn; for(int i=0;i<g[u].size();i++){ int v=g[u][i]; if(!num[v]){ dfs(v); low[u]=min(low[u],low[v]); } else if(!sccno[v]){ //处理回退边 low[u]=min(low[u],num[v]); } } if(low[u]==num[u]){ //栈底的点是SCC的祖先, cnt++; while(1){ int v=stack[top--]; sccno[v]=cnt; if(u==v) break; //到达了祖先 } } } void tarjan(int n){ cnt=top=dfn=0; memset(sccno,0,sizeof(sccno)); memset(num,0,sizeof(num)); memset(low,0,sizeof(low)); for(int i=1;i<=n;i++){ if(!num[i]) dfs(i); } } int main(){ int n,m,u,v; while(scanf("%d %d",&n,&m),n!=0||m!=0){ for(int i=0;i<=n;i++){ g[i].clear(); } for(int i=0;i<m;i++){ scanf("%d %d",&u,&v); g[u].push_back(v); } tarjan(n); if(cnt==1) printf("Yes\n"); else printf("No\n"); } return 0; }

猜你喜欢

转载自www.cnblogs.com/shirlybaby/p/12606018.html