[COCI2009-2010#6] HOLMES

written on 2022-07-18

应当说是一道想法题,想到了以后这题就不会太难了,因为涉及的算法也不是很高深。

首先稍稍转化一下题目,将这些事件转化为一个个节点,推论条件可视作有向边(这不显然)。然后不妨令某一个事件发生为某一个节点被标记,考虑对于每一个节点,如果其被标记,那么根据题意,其祖先中至少有 \(1\) 个是被标记了的。注意:只要祖先中有,那么该节点就是被标记的。

这个基本思路的瓶颈在于,如何知道一个点的祖先中是否有被标记了的节点。这时我们可以用逆向思维,如果存在,那么除去祖先集合以外的其他点被标记数之和一定等于当前个数 \(D\)。具体地,为了彻底排除祖先对外部的影响,采用拓扑排序,对于每一个入度为 \(0\) 的非祖先结点,加入队列,然后考察每一个加入队列的元素,如果被标记,\(cnt\) 累加,最后判断 \(cnt\) 是否等于 \(D\)。最后动态更新 \(D\) 以及标记数组 \(mark\)

如果还有不理解的地方,可以参照上述的方法手动模拟一下题面里的样例 \(4\),加深理解。另外,某一个点的祖先结点显然是可以提前 \(O(n)\) 预处理的,总时间复杂度 \(O(n^2)\)。具体可以参考代码。

#include<bits/stdc++.h>
#define N 1005
#define M 100005
using namespace std;
int n,m,d;
int tot,ver[M],nxt[M],head[N],deg[N];
void add_E(int x,int y){ver[++tot]=y,nxt[tot]=head[x],head[x]=tot;}
int tot2,ver2[M],nxt2[M],head2[N];
void add_E2(int x,int y){ver2[++tot2]=y,nxt2[tot2]=head2[x],head2[x]=tot2;}
bool mark[N],r[N],vis[N];
queue<int> q;
bool check(int S)
{
	int cnt=0;
	memset(r,0,sizeof(r));
	q.push(S);
	r[S]=1;
	while(q.size())
	{
		int x=q.front();
		q.pop();
		for(int i=head2[x];i;i=nxt2[i])
		{
			int y=ver2[i];
			if(!r[y]) r[y]=1,q.push(y);
		}
	}
	memset(vis,0,sizeof(vis));
	for(int i=1;i<=n;i++) if(!r[i]&&!deg[i]) q.push(i),vis[i]=1;
	while(q.size())
	{
		int x=q.front();
		q.pop();
		if(mark[x]) cnt++;
		for(int i=head[x];i;i=nxt[i])
		{
			int y=ver[i];
			if(!vis[y]&&!r[y]) vis[y]=1,q.push(y);
		}
	}
	return cnt<d;
}
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_E(x,y),deg[y]++;
		add_E2(y,x);
	}
	for(int i=1;i<=d;i++)
	{
		int x;
		scanf("%d",&x);
		mark[x]=1;
	}
	for(int i=1;i<=n;i++) if(check(i)&&!mark[i]) d++,mark[i]=1;
	for(int i=1;i<=n;i++) if(mark[i]) printf("%d ",i);
}
posted @ 2022-07-31 22:16  Freshair_qprt  阅读(25)  评论(0编辑  收藏  举报