Applese 的QQ群(dfs判环&拓扑排序判环/**详解*/)

链接:https://ac.nowcoder.com/acm/contest/330/F
来源:牛客网

  • 题目描述
    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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值