• 强连通分量


    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(' ');
    		}
    	}
    }
    

    心得

    • 有向无环图中一个出度为零的点,从任意一个点出发都可到达它。
    • 有向无环图至少有一个入度为零的点。
    • 有向无环图的生成树个数等于入度非零的节点的入度积。
    • 强联通分量的题好像多数与入度出度有关,想题的时候可以往这方面考虑。
  • 相关阅读:
    Hadoop 集群安装(从节点安装配置)
    Hadoop 集群安装(主节点安装)
    少儿编程(2):简单的数学计算
    少儿编程(1):计算思维
    Web测试入门:Selenium+Chrome+Python+Mac OS
    我为什么建议:在软工实践作业中增加性能测试分析的任务?
    基于码云开展程序设计教学的自动判分方法和代码框架?
    数值计算 的bug:(理论)数学上等价,实际运行未必等价
    【Alpha】Daily Scrum Meeting总结
    【Alpha】Daily Scrum Meeting第十次
  • 原文地址:https://www.cnblogs.com/doublety/p/11519454.html
Copyright © 2020-2023  润新知