洛谷 2244

                                                    这道题有很巧妙的结论,但还是难以掩盖它是乱搞题的本质-----By APT

      这道题看起来无从下手,因为关系很复杂,我先给出做法,再给出证明,首先我们将A赢B的关系表示成A-->B的有向边,我们找到出度最大的点,然后找到没有被它直接指向的点,然后这些点都是可能赢的点,我们将它们标记为能赢,然后放入队列,继续上述操作,最终就能得到答案。

      证明:首先由于我们一开始的点是出度最大点,也就是说没有其他点能够一下打败当前点和当前点能直接打败的点,因为如果存在这样的点,它的度数一定大于当前点。那么这样又如何呢?我们考虑剩下的所有点,为什么它们都可能赢呢,因为我们可以构造出一种方式令它们胜利,对于一个剩下的点,我们先让别的点都输掉,然后在让当前点把它能直接打败的点全打败,最后再让这个剩下的点打败当前点,于是它就赢了,然后我们对于每个能赢的点做这种操作,剩下的点都是可能打败当前能赢的点的点,我们先让能赢的点把其他点全打败,再让这个点打败能赢的点,这样它也能赢。于是我们这样一直推下去就可以了。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define maxn 1000005
int que[maxn+5],next[maxn],prev[maxn];
int pre[maxn],last[maxn],other[maxn],cd[maxn],l;
int n,m,cnt;
bool flag[maxn],ans[maxn];

int read(void)
{
	char ch=getchar();
	int x=0;
	while (ch<'0'||ch>'9') ch=getchar();
	while (ch>='0'&&ch<='9') 
	{
		x=x*10+ch-'0';
		ch=getchar();
	}
	return x;
}

void connect(int x,int y)
{
	l++;
	pre[l]=last[x];
	last[x]=l;
	other[l]=y;	
}

int main()
{
	//scanf("%d",&n);
	n=read();
	for (int i=1;i<=n;i++) 
	{
		//scanf("%d",&cd[i]);
		cd[i]=read();
		for (int j=1;j<=cd[i];j++) 
		{
			int a;
			//scanf("%d",&a);
			a=read();
			connect(i,a);
		}
		if (cd[i]>cd[que[1]]) que[1]=i;
	}
	for (int i=0;i<=n;i++) {prev[i]=i-1;next[i]=i+1;}
	next[n]=0;pre[0]=0;
	int h=0,t=1;
	while (h!=t) 
	{
		h=h%maxn+1;
		int u=que[h];
		for (int p=last[u];p;p=pre[p]) 
		{
			int v=other[p];
			flag[v]=1;
		}
		int w=next[0];
		while (w) 
		{
			if (!flag[w]) 
			{
				ans[w]=1;	
				cnt++;
				next[prev[w]]=next[w];
				prev[next[w]]=prev[w];
				t=t%maxn+1;
				que[t]=w;
			}
			w=next[w];
		}
		for (int p=last[u];p;p=pre[p]) 
		{
			int v=other[p];
			flag[v]=0;	
		}
	}
	printf("%d",cnt);
	for (int i=1;i<=n;i++) 
		if (ans[i]) printf(" %d",i);
	printf("\n");
	return 0;	
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值