zoukankan      html  css  js  c++  java
  • [JSOI2013]侦探 JYY

    P5954 [JSOI2013]侦探 JYY

    说明

    2020.9.14的考试题

    考场上没想出来,之后借鉴了学长的思路才改正。


    思路

    设给出的已知事件为 (x)

    • 朴素想法

    对于每个 (x),显然所有子孙是要选的,那么我们就只需要考虑祖先怎么选。

    首先任选一个父亲往上走直到走到一个入度为0的点 (top),然后再从 (top) 往下标记所有点(这些点就全部都要输出)

    如果此时还存在一个入度为0且没被标记过的点能够走到 (x),则说明 (x) 的父亲相互矛盾,即样例二的情况,那么所有的父亲都不能选

    但是,这样做就会遗漏掉一种情况,而且大概率会 (T)

    • 完整思路

    来看样例四,我们会发现:即使 (x=3) 的父亲相互矛盾,但是输出却不止3,而还有4

    这样我们的朴素算法就被 (Hack) 掉了,看一下图:

    归纳一下,样例四的情况即:即使当前 (x) 的所有父亲不能选,但是所有父亲有除 (x) 以外的公共儿子(例如点4),那么这些公共儿子也一定会被选

    这种情况怎么处理?

    设当前已经确定要输出的数的个数为 (sum)

    我们首先将每个点的所有祖先存入对应的 (set) 中,然后对于每个不是 (x) 的点 (i) 进行一次 (check),判断所有不在 (i) 的路径上的点中已经被确定的数的个数 (cnt) 是否等于 (sum)

    如果小于 (sum),则说明当前点 (i) 一定会被选中输出。因为剩余的被确定的数(即 (sum-cnt))只可能存在于当前点 (i) 所在的路径上,那么 (i) 一定会被选中

    (check) 算法框架:

    1. 找到除当前点 (i) 的父节点以外的入度为0的点

    2. 然后遍历找到的这些点的子孙

    3. 所有以上找到的点就是不存在于当前点 (i) 路径上的点

    有一点抽象,来看一个例子:

    则有:

    set[1]={1}
    set[2]={1,2}
    set[3]={1,2,3}
    set[4]={1,2,4}
    set[5]={5}
    set[6]={1,2,4,6}
    
    

    假设当前点 (i=2),那么 ({5,6}) 就是找到的不存在于 (i) 的路径上的点


    代码

    如果思路有些不懂也不要急,结合下面的代码理解,多画一下样例,就好了qwq

    #include <bits/stdc++.h>
    using namespace std;
    set<int> fa[20010];
    int d,m,n,x,y,tot,sum;
    int in[20010],vis[20010],head[200010],viss[20010];
    
    struct node {
    	int to,net;	
    } e[200010];
    
    void add(int u,int v) {
    	e[++tot].to=v;
    	e[tot].net=head[u];
    	head[u]=tot;
    }
    
    void bfs1(int now) {
    	queue<int> q;
    	memset(viss,0,sizeof(viss));
    	q.push(now);
    	viss[now]=1;
    	while(!q.empty()) {
    		int xx=q.front();
    		q.pop();
    		fa[xx].insert(now);
    		for(int i=head[xx];i;i=e[i].net) {
    			int v=e[i].to;
    			if(viss[v]) continue;
    			q.push(v);
    			viss[v]=1;
    		}
    	}
    }
    
    int check(int now) {
    	queue<int> q;
    	memset(viss,0,sizeof(viss));
    	for(int i=1;i<=d;i++) {
    		if(fa[now].find(i)==fa[now].end()&&!in[i]) q.push(i),viss[i]=1;  //除x的父节点以外入度为0的节点(即不在x的路径上) 
    	}
    	int cnt=0;
    	while(!q.empty()) {
    		int xx=q.front();
    		q.pop();
    		cnt+=vis[xx];  //统计其中被确定的数的个数 
    		for(int i=head[xx];i;i=e[i].net) {
    			int v=e[i].to;
    			if(viss[v]) continue;
    			viss[v]=1;
    			q.push(v);
    		}
    	}
    	return cnt;
    }
    
    void bfs2() {
    	queue<int> q;
    	for(int i=1;i<=d;i++) {
    		if(vis[i]) q.push(i);
    	}
    	while(!q.empty()) {
    		int xx=q.front();
    		q.pop();
    		for(int i=head[xx];i;i=e[i].net) {
    			int v=e[i].to;
    			if(vis[v]) continue;
    			vis[v]=1;
    			q.push(v);
    		}
    	}
    }
    
    int main() {
    	scanf("%d%d%d",&d,&m,&n);
    	for(int i=1;i<=m;i++) {
    		scanf("%d%d",&x,&y);
    		add(x,y);
    		in[y]++;
    	}
    	for(int i=1;i<=n;i++) {
    		scanf("%d",&x); 
    		vis[x]=1;  //所有被确定的事件不用管 
    		sum++;  //sum表示已经被确定的事件个数 
    	}
    	for(int i=1;i<=d;i++) bfs1(i);   //找到每个点i的所有祖先 
    	for(int i=1;i<=d;i++) {
    		if(vis[i]) continue;
    		if(check(i)<sum) {  //如果不在i的路径上的点中被确定的数的个数小于sum,则说明当前i一定会被选 
    			vis[i]=1;
    			sum++;
    		}
    	}
    	bfs2();   //最后遍历标记所有能被确定的数,输出 
    	for(int i=1;i<=d;i++) {
    		if(vis[i]) printf("%d ",i);
    	}
    	return 0;
    }
    

  • 相关阅读:
    准备将STM32的库文件版本升级到3.5
    Linux学习基础文章1:Linux一句话精彩问答
    Linux学习基础文章2:Linux必学的60个命令
    以太网基础知识:TCP与UDP的区别
    在Keil uv4里面添加STC元器件库,不影响其他元件
    天气凉爽,开始锻炼身体
    STM32驱动12bit AD TLC2543(I/O模拟方式)
    用FATFS文件系统写SD卡的txt文档的问题
    LPC21XX系列ARM7驱动RTC RX8025(I/O模拟IIC)
    一些常见的问题与解决代码!(精典) 4
  • 原文地址:https://www.cnblogs.com/Eleven-Qian-Shan/p/13669691.html
Copyright © 2011-2022 走看看