• 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;
    }
    
  • 相关阅读:
    《构建之法》第1.2.3章读后感以及《硅谷传奇》观后感
    算复利条件下等额还款金额
    统计实验数据
    单利计算与复利计算程序
    了解和熟悉操作系统
    0302思考并回答一些问题
    sae storage 使用uploadify插件进行文件批量上传
    PHP页面之间跳转方法总结
    js获取每个按键的ASCII值
    C#文件的拆分与合并操作示例
  • 原文地址:https://www.cnblogs.com/Serene-shixinyi/p/9071584.html
Copyright © 2020-2023  润新知