- 题目描述
pplese 有一个QQ群。在这个群中,大家互相请教问题。如 b 向 a 请教过问题,就把 a 叫做是 b 的"老板"。这样一个群中就会有很多老板。
同时规定:如果 a 是 b 的老板,b 是 c 的老板,那么 a 也是 c 的老板。
为了不破坏群里面和谐交流的氛围,Applese 定了一个群规:不允许出现 a 既是 b 的老板, b 又是 a 的老板。
你需要帮助 Applese 判断大家是否遵守了群规。- 输入描述:
第一行两个整数 n, m,表示群里的人数以及请教问题的数量。
接下来 m 行,每行两个整数 a, b,表示 a 是 b 的"老板",即 b 向 a 请教了一个问题。
注:无论是否违反了群规,a 都会成为 b 的老板。- 输出描述:
对于每次提问,输出一行"Yes"表示大家都遵守了群规,反之输出"No"。- 输入
4 4
1 2
2 3
3 1
1 4- 输出
Yes
Yes
No
No- 备注:1≤n≤105,1≤n≤105,1≤m≤2⋅105,1≤m≤2⋅105,1≤a,b≤n
刚开始以为是并查集水了一波没过去就没有思路去做这道题了,此题的正解其实是判断一个有向图是否有环,对于这道题,我们又不能加一条边判断一次,由题目我们可以发现在加边的时候,只要有一条边加进去存在了环,后面的加的所有边都会输出No,所以我们可以通过二分+拓扑排序的方法来找到不存在环的最后一条边.
- 拓扑排序过程
在一个有向图中选择一个入度为零的点并且输出,从图(vector建图)中删除这个点和所有以它为尾的边,一直重复上述过程直到不存在没有前驱的顶点。如果此时输出的顶点数小于有向图中的顶点数,说明图中存在环,否则输出的顶点序列是拓扑序列。
//二分+拓扑排序(离线计算)
#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<cmath>
#include<cstring>
#include<queue>
#include<vector>
#define pi 3.1415926
#define mod 1000000007
using namespace std;
//typedef pair<int,int> Node;
typedef long long LL;
const int Max_n=100005;
int n,m;
int in[Max_n];//每个点的入度
struct Edge{
int s,e;
}edge[Max_n<<1];//m<2*10^5
int check(int mid){//拓扑排序判环
memset(in,0,sizeof(in));
vector<int>v[Max_n];
for(int i=1;i<=mid;i++){//mid及mid之前的数据是否存在环
v[edge[i].s].push_back(edge[i].e);//建图(类似与邻接表)
in[edge[i].e]++;
}
int sum=0;//拓扑排序所有能够出来的点的总数
queue<int>q;
for(int i=1;i<=n;i++)//如果有入度为0的点,则加入到队列中
if(!in[i]) q.push(i);
while(q.size()){
int x=q.front();q.pop();//入度为0的点出队
sum++;//总数+1
for(unsigned int i=0;i<v[x].size();i++){//注意这里从0开始
//所有与x相连接的点入度-1
in[v[x][i]]--;
if(in[v[x][i]]==0) q.push(v[x][i]);
}
}
return sum==n;//出来的所有点的总数为n说明不存在环,否则存在环
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
scanf("%d%d",&edge[i].s,&edge[i].e);
int l=1,r=m,ans;
while(l<=r){
int mid=l+(r-l)/2;
if(check(mid)){//此时中点前面不存在环,存在环的点在右边
l=mid+1;
ans=mid;//记录不存在环的最后一个点
}else{//中点之前存在环
r=mid-1;
}
}
for(int i=1;i<=ans;i++) printf("Yes\n");
for(int i=ans+1;i<=m;i++) printf("No\n");
return 0;
}
我们还可以用dfs的方法来判断图中是否存在环,v[b].push_back(a);反向把边存起来表示b的老板是a,在dfs的时候我们只要找到a的老板是不是b即可,如果是说明存在环,否则说明不存在环.
//dfs判环
#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<cmath>
#include<cstring>
#include<queue>
#include<vector>
#define pi 3.1415926
#define mod 1000000007
using namespace std;
//typedef pair<int,int> Node;
typedef long long LL;
const int Max_n=100005;
vector<int>v[Max_n];
int dfs(int a,int b){
for(unsigned int i=0;i<v[a].size();i++){
if(v[a][i]==b) return 1;//a的老板是b
if(dfs(v[a][i],b)) return 1;//dfs a的老板的老板是b 返回1
}
return 0;
}
int main(){
int n,m;
scanf("%d%d",&n,&m);
int flag=0;
while(m--){
int a,b;
scanf("%d%d",&a,&b);//a是b的老板
if(flag||dfs(a,b)){//dfs a的老板是不是b
flag=1;//一旦存在环,后面的输入全部存在环
printf("No\n");//如果是存在环
}else{//b不是a的老板反向存边
v[b].push_back(a);//b的老板是a
printf("Yes\n");
}
}
return 0;
}