题目链接: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;
}