POJ 2762 Going from u to v or from v to u?

题目链接:http://poj.org/problem?id=2762

题意:给出一个图及其相连的边,判断是否任意两点之间都是相通的(能从x到y或者能从y到x)。

题解:可以知道每个强连通分中的点必然都能够相互到达,所以先对图进行缩点,缩点之后是一个有向无环图DAG,构造新图,任意两个点相互可达转化为任意两个连通分量相互可达,即新图中任意两个点可达。

对新图进行拓扑排序,排序之后如果任意相邻两点之间存在边,相当于存在一条从一个点途径其余所有点到达另一个点的一条“”,则这个新图任意两点都是是相互可达的。

想象一下拓扑排序后相邻两点不存在边的情况,其实就是当寻找当前入度为0的点的时候找到的点不止一个,则这两个点必定不连通。如下图,当1,2排序之后,发现有两个入度为0的点3和4,则这两个点排完序之后虽然相邻,但是并不相通。


但是其实这道题不用实际的拓扑排序,因为我们知道其实Tarjan算法求出的连通分量的顺序就是这个新图的拓扑排序的逆。(可以看这篇博客

代码:

用拓扑排序。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<stack>
using namespace std;
const int MAX=1000+10;
vector<int> g[MAX];
stack<int> st;
int dfn[MAX],low[MAX],instack[MAX];
int sccno[MAX];
int dfs_cnt,scc_cnt;

int G[MAX][MAX];
int in[MAX];
int topo[MAX];
int n,m;

void dfs(int u)
{
	dfn[u]=low[u]=++dfs_cnt;
	st.push(u);
	instack[u]=1;
	for(int i=0;i<g[u].size();i++)
	{
		int v=g[u][i];
		if(!dfn[v])
		{
			dfs(v);
			low[u]=min(low[u],low[v]);
		}
		else if(instack[v])
		{
			low[u]=min(low[u],dfn[v]);
		}
	}
	if(dfn[u]==low[u])
	{
		int v;
		scc_cnt++;
		do
		{
			v=st.top();
			st.pop();
			instack[v]=0;
			sccno[v]=scc_cnt;
		}while(u!=v);
	}
}
void tarjan()
{
	dfs_cnt=scc_cnt=0;
	memset(dfn,0,sizeof(dfn));
	memset(instack,0,sizeof(instack));
	memset(sccno,0,sizeof(sccno));
	while(!st.empty()) st.pop();
	for(int i=1;i<=n;i++)
	{
		if(!dfn[i]) dfs(i); 
	}

}
void toposort()
{
	for(int i=1;i<=scc_cnt;i++)
	{
		for(int j=1;j<=scc_cnt;j++)
		{
			in[j]+=G[i][j];
		}
	}
	for(int k=0;k<scc_cnt;k++)
	{
		int i,j;
		for(i=1;in[i]&&i<=scc_cnt;i++);
		if(i>scc_cnt) return ;
		topo[k]=i;
		in[i]=-1;
		for(j=1;j<=scc_cnt;j++)
		{
			if(G[i][j])
			{
				in[j]--;
			}
		}
	}
}
int main()
{
	//freopen("in.txt","r",stdin);
	//freopen("out.txt","w",stdout);
	int T;
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d%d",&n,&m);
		memset(G,0,sizeof(G));
		memset(in,0,sizeof(in));
		memset(topo,0,sizeof(topo));
		for(int i=1;i<=n;i++)
			g[i].clear();
		for(int i=0;i<m;i++)
		{
			int a,b;
			scanf("%d%d",&a,&b);
			g[a].push_back(b);
		}
		tarjan();
		for(int i=1;i<=n;i++)
			for(int k=0;k<g[i].size();k++)
			{
				int j=g[i][k];
				if(sccno[i]!=sccno[j])
					G[sccno[i]][sccno[j]]=1;
			}
		toposort();
		int flag=1;
		for(int i=1;i<scc_cnt;i++)
		{
			if(!G[topo[i-1]][topo[i]])
			{
				flag=0;
				break;
			}
		}
		printf("%s\n",flag?"Yes":"No");
	}
	return 0;
}
不用拓扑排序,直接判断:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#include <stack>
using namespace std;
const int MAX=1000+10;
vector<int> g[MAX];
stack<int> st;
int dfn[MAX],low[MAX],instack[MAX];
int sccno[MAX];
int dfs_cnt,scc_cnt;

int G[MAX][MAX];
int n,m;

void dfs(int u)
{
	dfn[u]=low[u]=++dfs_cnt;
	st.push(u);
	instack[u]=1;
	for(int i=0;i<g[u].size();i++)
	{
		int v=g[u][i];
		if(!dfn[v])
		{
			dfs(v);
			low[u]=min(low[u],low[v]);
		}
		else if(instack[v])
		{
			low[u]=min(low[u],dfn[v]);
		}
	}
	if(dfn[u]==low[u])
	{
		int v;
		scc_cnt++;
		do
		{
			v=st.top();
			st.pop();
			instack[v]=0;
			sccno[v]=scc_cnt;
		}while(u!=v);
	}
}
void tarjan()
{
	dfs_cnt=scc_cnt=0;
	memset(dfn,0,sizeof(dfn));
	memset(instack,0,sizeof(instack));
	memset(sccno,0,sizeof(sccno));
	while(!st.empty()) st.pop();
	for(int i=1;i<=n;i++)
	{
		if(!dfn[i]) dfs(i); 
	}
}
int main()
{
	//freopen("in.txt","r",stdin);
	//freopen("out.txt","w",stdout);
	int T;
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d%d",&n,&m);
		memset(G,0,sizeof(G));
		for(int i=1;i<=n;i++)
			g[i].clear();
		for(int i=0;i<m;i++)
		{
			int a,b;
			scanf("%d%d",&a,&b);
			g[a].push_back(b);
		}
		tarjan();
		for(int i=1;i<=n;i++)
			for(int k=0;k<g[i].size();k++)
			{
				int j=g[i][k];
				if(sccno[i]!=sccno[j])
					G[sccno[i]][sccno[j]]=1;
			}
		int flag=1;
		for(int i=2;i<=scc_cnt;i++)
		{
			if(!G[i][i-1])
			{
				flag=0;
				break;
			}
		}
		printf("%s\n",flag?"Yes":"No");
	}
	return 0;
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值