1 条题解
-
1
说明
2020.9.14的考试题
考场上没想出来,之后借鉴了学长的思路才改正。
思路
设给出的已知事件为
- 朴素想法
对于每个 ,显然所有子孙是要选的,那么我们就只需要考虑祖先怎么选。
首先任选一个父亲往上走直到走到一个入度为0的点 ,然后再从 往下标记所有点(这些点就全部都要输出)
如果此时还存在一个入度为0且没被标记过的点能够走到 ,则说明 的父亲相互矛盾,即样例二的情况,那么所有的父亲都不能选
但是,这样做就会遗漏掉一种情况,而且大概率会 掉
- 完整思路
来看样例四,我们会发现:即使 的父亲相互矛盾,但是输出却不止3,而还有4
这样我们的朴素算法就被 掉了,看一下图:

归纳一下,样例四的情况即:即使当前 的所有父亲不能选,但是所有父亲有除 以外的公共儿子(例如点4),那么这些公共儿子也一定会被选
这种情况怎么处理?
设当前已经确定要输出的数的个数为
我们首先将每个点的所有祖先存入对应的 中,然后对于每个不是 的点 进行一次 ,判断所有不在 的路径上的点中已经被确定的数的个数 是否等于
如果小于 ,则说明当前点 一定会被选中输出。因为剩余的被确定的数(即 )只可能存在于当前点 所在的路径上,那么 一定会被选中
算法框架:
-
找到除当前点 的父节点以外的入度为0的点
-
然后遍历找到的这些点的子孙
-
所有以上找到的点就是不存在于当前点 路径上的点
有一点抽象,来看一个例子:

则有:
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}假设当前点 ,那么 就是找到的不存在于 的路径上的点
代码
如果思路有些不懂也不要急,结合下面的代码理解,多画一下样例,就好了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; }
- 1
信息
- ID
- 10791
- 时间
- 500ms
- 内存
- 32MiB
- 难度
- 10
- 标签
- 递交数
- 4
- 已通过
- 2
- 上传者