agc016E Poor Turkeys

题意:有n只火鸡,m个猎人按序来杀火鸡,第$i$个人从$x_i,y_i$中选一只杀,如果$x_i,y_i$中只有1只存活就杀活的那只,如果都死了就什么都不做,问有多少对火鸡对可以同时存活

$n leq 400 , m leq 1e5$

很有趣的一道题呀……

感觉n很小,m比较大,$nm$和$n^3$的算法感觉应该都可以。

我们先考虑判断每个火鸡是否有可能存活。

我们发现,顺推是不好推的,因为前面的决定会影响后面的决定,我们不知道当前决定是不是合法的。

所以考虑倒推。

假如我们现在需要判断火鸡$X$是否能够存活

我们维护一个集合$S_X$,在最开始的时候$S={ X }$,表示最后$X$需要存活

我们倒着考虑每一个猎人,假设我们考虑到第$p$个猎人,当前集合$S$表示第$p$个猎人来后,需要存活的火鸡。

我们可以根据$x_p,y_p$倒推出第$p-1$个猎人来后,需要存活的火鸡,也就是向上一步的$S$集合。

存在以下三种情况:

1、$x_p in S , y_p in S$  火鸡$x$不能存活

2、$x_p in S , y_p otin S$ 或$x_p otin S , y_p in S$ ,把不在$S$的那个加入$S$

3、$x_p otin S , y_p otin S$ 什么都不做

我们发现,$S$集合扩大的情况只有情况2,而情况2可以这样理解:

假如现在是$x_p in S$ (因为$y_p in S$的情况可以同理),那么此时为了让$x_p$存活下去,$y_p$在此时必须死,不能早死也不能晚死,它就是为$x_p$而死的。

如果每遇到这种情况,我们就连一条边,$x_p -> y_p$,那么最终我们会得到一棵树,以$X$为根,一个点的儿子就是为它而死的火鸡,可能有多个。

我们计算答案时,可以直接枚举点对$(a,b)$(满足$a e b$),如果a、b都可能存活,并且$S_a cap S_b = emptyset$,那么$a,b$最后可以同时存活,否则不能同时存活。

满足$S_a cap S_b = emptyset$这个条件时,显然$a,b$都可以存活

为什么如果不满足这个条件就不能同时存活呢。我们用反证法证明。

假如说有个火鸡$r$,满足$r in S_a , r in S_b$,并且最后$a,b$可以同时存活

我们知道,在$r$加入$S_a$的时候就是$r$死的时候,在$r$加入$S_b$的时候也是$r$死的时候

所以说$r$在$S_a$中加入和在$S_b$中加入一定要是同一时间,那么是在同一个猎人到来时,为了同一只火鸡而死的。

那么我们就找到了$r$在两颗树上的父亲,这两个父亲是相同的,假设是$t$。

于是同理我们发现两棵树中$t$的父亲是相同的,这样一直推,就会推到根,要么是第一棵树的根$a$,要么是第二棵树的根$b$

那么要么$a$在$S_a$和$S_b$中都出现,要么$b$在$S_a$和$S_b$中都出现。

也就是要么$a$要为$b$的最终存活而死,要么$b$要为$a$的最终存活而死,反正$a,b$不能在最后同时存活。

然后维护集合$S$我们可以用bitset。

//Serene
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<cmath>
#include<bitset>
using namespace std;
#define ll long long
#define db double
#define For(i,a,b) for(int i=(a);i<=(b);++i)
#define Rep(i,a,b) for(int i=(a);i>=(b);--i)
const int maxn=400+7,maxm=1e5+7;
int n,m,X[maxm],Y[maxm],ans;
bool ok[maxn];
bitset<maxn> G[maxn];

char cc; ll ff;
template<typename T>void read(T& aa) {
	aa=0;cc=getchar();ff=1;
	while((cc<'0'||cc>'9')&&cc!='-') cc=getchar();
	if(cc=='-') ff=-1,cc=getchar();
	while(cc>='0'&&cc<='9') aa=aa*10+cc-'0',cc=getchar();
	aa*=ff;
}

int main() {
	read(n); read(m);
	For(i,1,m) read(X[i]),read(Y[i]);
	For(i,1,n) {
		G[i].reset(); G[i].set(i);
		Rep(j,m,1) {
			if(G[i][X[j]]&&G[i][Y[j]]) {ok[i]=1;break;}
			if(G[i][X[j]]) G[i].set(Y[j]);
			if(G[i][Y[j]]) G[i].set(X[j]);
		}
	}
	For(i,1,n) if(!ok[i]) For(j,i+1,n) if(!ok[j])
		if((G[i]&G[j]).count()==0) ans++;
	printf("%d
",ans);
	return 0;
}
原文地址:https://www.cnblogs.com/Serene-shixinyi/p/9071584.html