CF1019C

好玄学的东西...

核心思想:for循环!

首先,我们从前向后扫所有的点,如果这个点没被标记成不可用就把这个点标记成已使用,然后把所有与这个点直接相连的点标记成不可用

接下来,我们从后向前扫所有的点,如果这个点被标记成已使用就把与这个点所有直接相连的也被标记已使用的点达成不能使用,最后标记成可使用的就是集合中的点

证明一下这个算法的正确性:

首先,经过第一步操作后,第二个要求一定能够满足,因为现在集合中的点和集合外的点的距离至多是1!(如果这个点不在集合里,说明这个点一定已经被一个在集合里的点标记上了,所以集合里的点到集合外的点的距离最大是1)

可是这样做会产生一些不合法的情况,这些情况的产生原因是我们在进行第一步操作时,只能够排除由编号较小的点向编号较大的点连边的情况,但是如果有编号较大的点向编号较小的点连边这种情况是无法处理的

所以我们第二次再从后向前扫点,将与扫到的所有计划使用的点有连边的点标记为不可用,这样就能保证集合中的点互相没有连边了

至于这么做是否能满足距离的要求,我们思考:经过第一步操作后,我们能保证的是所有集合外的点与集合内的点距离为1

那么我们删除一个集合内的点的条件这个点与集合中别的点有连边,也就是说如果这个点被删掉了,这个点与集合的距离就会变成1,那么最糟的情况无非就是另一个集合外的点只与这个点相连,这样的情况的话外面的点到集合的距离无非就是2,所以也是合法的,也就是说,这样操作后获得的情况一定是一种合法情况!

#include <cstdio>
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#include <queue>
#include <stack>
using namespace std;
struct Edge
{
	int next;
	int to;
}edge[1000005];
int head[1000005];
bool used[1000005];
bool vis[1000005];
int cnt=1;
int n,m;
void init()
{
	memset(head,-1,sizeof(head));
	cnt=1;
}
void add(int l,int r)
{
	edge[cnt].next=head[l];
	edge[cnt].to=r;
	head[l]=cnt++;
}

int main()
{
	scanf("%d%d",&n,&m);
	init();
	for(int i=1;i<=m;i++)
	{	
		int x,y;	
		scanf("%d%d",&x,&y);
		add(x,y);
	}
	for(int i=1;i<=n;i++)
	{
		if(!used[i])
		{
			vis[i]=1;
			used[i]=1;
			for(int j=head[i];j!=-1;j=edge[j].next)
			{
				int to=edge[j].to;
				used[to]=1;
			}
		}
	}
	for(int i=n;i>=1;i--)
	{
		if(vis[i])
		{
			for(int j=head[i];j!=-1;j=edge[j].next)
			{
				int to=edge[j].to;
				vis[to]=0;
			}
		}
	}
	int ret=0;
	for(int i=1;i<=n;i++)
	{
		if(vis[i])
		{
			ret++;
		}
	}
	printf("%d\n",ret);
	for(int i=1;i<=n;i++)
	{
		if(vis[i])
		{
			printf("%d ",i);
		}
	}
	return 0;
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值