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

来源:互联网 发布:php 打开文件过程 编辑:程序博客网 时间:2024/06/03 16:57
Going from u to v or from v to u?
Time Limit: 2000MS Memory Limit: 65536KTotal Submissions: 17644 Accepted: 4739

Description

In 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?

Input

The 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. 

Output

The output should contain T lines. Write 'Yes' if the cave has the property stated above, or 'No' otherwise.

Sample Input

13 31 22 33 1

Sample Output

Yes


题目描述
给一个有向图,有 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;}


1 0
原创粉丝点击