您的位置:首页 > 编程语言 > Go语言

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

2017-04-27 16:33 288 查看
Going from u to v or from v to u?
Time Limit: 2000MS Memory Limit: 65536K
Total Submissions: 17644 Accepted: 4739
DescriptionIn order to make their sons brave, Jiajia and Wind take them to a big cave. The cave has n rooms, and one-way corridors connecting some rooms. Each time, Wind choose two rooms x and y, and ask one of their little sons go from one to the other. The son can either go from x to y, or from y to x. Wind promised that her tasks are all possible, but she actually doesn't know how to decide if a task is possible. To make her life easier, Jiajia decided to choose a cave in which every pair of rooms is a possible task. Given a cave, can you tell Jiajia whether Wind can randomly choose two rooms without worrying about anything?InputThe first line contains a single integer T, the number of test cases. And followed T cases. 

The first line for each case contains two integers n, m(0 < n < 1001,m < 6000), the number of rooms and corridors in the cave. The next m lines each contains two integers u and v, indicating that there is a corridor connecting room u and room v directly. 
OutputThe output should contain T lines. Write 'Yes' if the cave has the property stated above, or 'No' otherwise.Sample Input1
3 3
1 2
2 3
3 1Sample OutputYes
题目描述
给一个有向图,有 n 个点,m 条有向边。对于图中任意两个点 u,v ,如果从 u 能到 v ,或者 从 v 能到 u ,则称这对顶点是可行的,如果图中任意一对顶点都是可行的,可以输出“Yes”,否则输出“No”。
注意,这里是 u 到达 v ,或者 v 到达 u ,是“或者”不是“而且”!

输入格式
输入第一行为一个整数 T , 表示数据组数。
每组数据第一行为两个整数 n 和 m(0<n<1001;m<6000),分别表示点和边的数量。
接下来有 m 行,每行两个整数 u 和 v ,表示从 u 到 v 有一条有向边相连。

输出格式
如果图中任意一对顶点都是可行的,可以输出“Yes”,否则输出“No”。

   这就不是裸的强连通分量了,刚做这道题不知道怎么处理,后来在网上看到了别人的思路才弄懂,感觉挺巧妙。   思考一下,对于一个有向图而言,里面的强连通分量肯定满足题目的条件,因为强连通分量内是u可到v,而且,v可到u,所以我们第一步工作就是强连通分量缩点,变为一个DAG(即:有向无环图,Directed Acyclic Graph)。对于这个DAG里面的任意两个点U,V,如果满足题目的条件,那么点U,V里面包含的原图的点也肯定是满足的所以问题转化为怎么判断这个DAG是满足题目条件呢?其实判断很简单,就是看这个DAG里面有多少个点的入度为0,多少个点出度为0。

如果DAG中,有2个或2个以上的点的入度为0,那么这个DAG就是No,因为这说明有两个点是无法“进去”的,没有点能“进去”U,也没有点能“进去”V,那么这两个点肯定互相不能“进去”,即到达如果DAG中,有2个或2个以上的点的出度为0,那么这个DAG就是No,因为说明这两个点是无法“出去的”。

反证法,点U和V,它们出度都是0,假设它们满足题目条件,从U能到V,或者从V能到U。
1.因为点U出度为0,所以无法“出去”,它肯定无法到达V。
2.因为点V出度为0,所以无法“出去”,它肯定无法到达U。
3.综上,点U无法到达V,点V无法到达点U,与假设矛盾,所以与题目不成立。

那么什么最后一个问题,如果这个DAG,入度为0的点的个数 <= 1 , 出度为0的点的个数 <= 1 , 它就一定Yes吗?

这个就比较容易想了,首先,1个DAG中,不可能所有点都有入度,即入度为0的点个数为0,是不可能发生的。同样的,可能所有点都有出度,即出度为0的点个数为0,是不可能发生的。

那剩下的情况就是,1个点入度为0为u,1个点出度为0为v。这个图,呈现一个“橄榄形”。如果从点u走到点v,能经过所有的点,那么就是Yes,否则是No。因为无法经过所有的点的话,说明图中出现了“分叉”,这种“分叉”必定导致两个点互相不可达。而从u到v,经过所有点,其实就是把这个DAG变成了一条链,就是判断这个DAG是否存在一条链贯穿所有点。

怎么判断链呢?可以转化为拓扑排序,或者DP最长路,在这里就只说拓扑排序。

拓扑:拓扑排序可能有多种结果,但是如果是一个最长链,结果则是唯一的,结果要唯一,其实就是从初始化开始,到后面的任何一次删弧入队操作,都是保证了每次都只有一个元素入队,即永远只有一个元素的入度为0,如果中途有一次出现了有两个点入度为0,那么其实代表出现了一种“分叉”。

而且拓扑排序可以综合掉前面的情况,不需要特判DAG有几个点入度为0,出度为0,直接拓扑,在拓扑过程就已经判断了
下面是代码:#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <cmath>
#include <cctype>
#include <ctime>
#include <queue>
using namespace std;

int n,m,t,index,top,s,sum;
int first[10001],father[10001],zhan[10001];
int low[10001],num[10001];
bool dian[10001];
struct tarjan{int x,y,next;};
tarjan bian[100001];
int topzhan[10001],r[10001],a[1001][1001];

void clean()
{
index=0;
top=0;
s=0;
sum=0;
memset(first,-1,sizeof(first));
memset(father,0,sizeof(father));
memset(zhan,0,sizeof(zhan));
memset(low,0,sizeof(low));
memset(num,0,sizeof(num));
memset(dian,false,sizeof(dian));
memset(bian,0,sizeof(bian));
memset(topzhan,0,sizeof(topzhan));
memset(r,0,sizeof(r));
memset(a,0,sizeof(a));
}

void build(int x,int y)
{
s++;
bian[s].next=first[x];
first[x]=s;
bian[s].x=x;
bian[s].y=y;
}

void dfs(int x) //Tarjan
{
index++;
low[x]=index;
num[x]=index;
top++;
zhan[top]=x;
dian[x]=true;

int i,k;
k=first[x];
while(k!=-1)
{
if(num[bian[k].y]==0)
{
dfs(bian[k].y);
low[x]=min(low[x],low[bian[k].y]);
}
else if(dian[bian[k].y]) low[x]=min(low[x],num[bian[k].y]);
k=bian[k].next;
}

if(low[x]==num[x])
{
sum++;
i=zhan[top];
while(i!=x)
{
father[i]=sum;
dian[i]=false;
top--;
i=zhan[top];
}
father[i]=sum;
dian[i]=false;
top--;
}
}

int topsort() //拓扑排序
{
int tot=0,number=0;
for(int i=1;i<=sum;i++) if(r[i]==0) {number++; tot++; topzhan[tot]=i;}
if(number>1) return 0; //有两个或两个以上入度为0的点

number=0;
while(tot>0)
{
int u=topzhan[tot];
tot--;
for(int i=1;i<=a[u][0];i++)
{
r[a[u][i]]--;
if(r[a[u][i]]==0) {number++; tot++; topzhan[tot]=a[u][i];}
}
if(number>1) return 0; //删弧后,同时出现了两个点入度为0
number=0;
}
return 1;
}

int main()
{
//freopen("lx.in","r",stdin);
//freopen("lx.out","w",stdout);

scanf("%d",&t);
for(int i=1;i<=t;i++)
{
clean();
scanf("%d%d",&n,&m);
for(int j=1;j<=m;j++)
{
int x,y;
scanf("%d%d",&x,&y);
build(x,y);
}

for(int j=1;j<=n;j++) if(num[j]==0) dfs(j);

for(int j=1;j<=n;j++)
for(int k=first[j];k!=-1;k=bian[k].next)
{
int u,v;
u=father[j];
v=father[bian[k].y];
if(u!=v) {r[v]++; a[u][0]++; a[u][a[u][0]]=v;} //重新建图
}

int ans=topsort();
if(ans) cout<<"Yes"<<endl;
else cout<<"No"<<endl;
}

return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: