P5954 【[JSOI2013]侦探 JYY】
来自一位神仙学长的思路,题意就不说了
首先当前点被确定,那么他的所有儿子节点可以被确定
考虑父亲节点。当存在两个以及两个以上的入度为\(0\)的点可以到达当前节点时,该点的父亲节点是不可以确定的,但是也许会推出其他有用的信息
当数据是上面这样的时候,\(3\)是被确定的,\(1\)和\(2\)可能被确定,但是\(4\)却能被确定,答案为\(3\),\(4\)。这应该是一组有用的数据,可以帮助更深的理解
那么我们如何除了这些情况呢?
我们可以预处理出所有节点的父亲节点(包括自己和祖先),并计算出最初的可以被确定的点的数量\(sum\)(即题目中的\(b\))
假设当前处理的点为\(x\)(\(x\)没被确定),我们把所有的点分为两类
-
\(x\)的父亲节点和部分子节点
-
除\(x\)的父亲节点外中入读为\(0\)的点以及这些点的子节点
可能说得不太清楚,画图来理解
红色的即为第二类点,蓝色为第一类点,方框内的表示与\(x\)完全没有关系的点
那么我们可以求出红色类点中,已经被确定的点的个数\(tot\):
-
当\(tot=sum\),说明蓝色类点中,没有可以被确定的点
-
当\(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;
}