P5954 【[JSOI2013]侦探 JYY】

来自一位神仙学长的思路,题意就不说了

首先当前点被确定,那么他的所有儿子节点可以被确定

考虑父亲节点。当存在两个以及两个以上的入度为\(0\)的点可以到达当前节点时,该点的父亲节点是不可以确定的,但是也许会推出其他有用的信息

当数据是上面这样的时候,\(3\)是被确定的,\(1\)\(2\)可能被确定,但是\(4\)却能被确定,答案为\(3\)\(4\)。这应该是一组有用的数据,可以帮助更深的理解

那么我们如何除了这些情况呢?

我们可以预处理出所有节点的父亲节点(包括自己和祖先),并计算出最初的可以被确定的点的数量\(sum\)(即题目中的\(b\))

假设当前处理的点为\(x\)\(x\)没被确定),我们把所有的点分为两类

  1. \(x\)的父亲节点和部分子节点

  2. \(x\)的父亲节点外中入读为\(0\)的点以及这些点的子节点

可能说得不太清楚,画图来理解

红色的即为第二类点,蓝色为第一类点,方框内的表示与\(x\)完全没有关系的点

那么我们可以求出红色类点中,已经被确定的点的个数\(tot\):

  1. \(tot=sum\),说明蓝色类点中,没有可以被确定的点

  2. \(tot<sum\),说明蓝色类点中有其它已经被确定的点

因为\(x\)是没有被确定的点(见上),那么与蓝色类点中其他被确定的点一定能推出来\(x\),这个结论也不是特别显然,建议自己手模一下

由以上结论,我们可以知道\(x\)已经可以被确定了,此时\(sum++\)

对于每个点进行上述处理之后,我们再对每一个被确定的点进行一次搜索,他的子节点一定是可以被确定的

最后输出,结束

可能不太清楚的可以看代码注释,结合上面分析

#include<bits/stdc++.h>
using namespace std;
int head[200010],tot;
struct node{
	int net,to;
}e[200010];
int in[20010];
void add(int x,int y){
	e[++tot].net=head[x];
	e[tot].to=y;
	head[x]=tot;
}
int n,m,d;
bool v[20010],vis[20010];
set<int>fa[20010];
void bfs(int x){
	memset(vis,false,sizeof vis);
	queue<int>q;
	q.push(x);
	vis[x]=true;
	while(!q.empty()){
		int u=q.front();
		q.pop();
		fa[u].insert(x);
		for(int i=head[u];i;i=e[i].net){
			int y=e[i].to;
			if(vis[y]) continue;
			q.push(y);
			vis[y]=true; 
		}
	}
}
int sum;
int fin(int x){
	memset(vis,false,sizeof vis);
	queue<int>q;
	for(int i=1;i<=n;i++){
		if(fa[x].find(i)==fa[x].end()&&!in[i]) q.push(i),vis[i]=true;
		//表示红色类的点 
	}
	int tal=0;
	while(!q.empty()){
		int u=q.front();
		q.pop();
		tal+=v[u]; //统计个数 
		for(int i=head[u];i;i=e[i].net){
			int y=e[i].to;
			if(vis[y]) continue;
			q.push(y);
			vis[y]=true;
		}
	}
	return tal;
}
void bfs2(){
	queue<int>q;
	for(int i=1;i<=n;i++){
		if(v[i]){
			q.push(i);
		}
	}
	while(!q.empty()){
		int x=q.front();
		q.pop();
		for(int i=head[x];i;i=e[i].net){
			int y=e[i].to;
			if(v[y]) continue;
			v[y]=true;
			q.push(y);
		}
	} 
}
int main(){
	scanf("%d%d%d",&n,&m,&d);
	for(int i=1;i<=m;i++){
		int x,y;
		scanf("%d%d",&x,&y);
		add(x,y);
		in[y]++; //入度 
	}
	for(int i=1;i<=d;i++){
		int x;
		scanf("%d",&x);
		v[x]=true; //可以被确定 
		sum++; //已经确定的个数 
	}
	for(int i=1;i<=n;i++) bfs(i); //处理出每个点的父节点 
	for(int i=1;i<=n;i++){
		if(v[i]) continue;
		if(fin(i)<sum){ //蓝色类点中有被确定的点 
			v[i]=true;
			sum++; //当前点可以被确定 
		}
	}
	bfs2(); //把子节点标记一下 
	for(int i=1;i<=n;i++){
		if(v[i]) cout<<i<<" ";
	}
	return 0;
}
posted @ 2020-09-14 22:03  Poetic_Rain  阅读(21)  评论(0编辑  收藏