题意:有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; }