题解 AT2389 【[AGC016E] Poor Turkeys】
AT2389 [AGC016E] Poor Turkeys解题报告:
更好的阅读体验
题意
- 给定
n 只火鸡,m 个操作,每次操作会选择两只火鸡a,b ; - 如果两只都活着,那么任意吃一只;如果只活了一只,那么吃剩下一只;如果两只都死了,那么不吃;
- 求数对
(i,j) 满足i<j 且存在一种情况i 和j 同时活着的数量。 - 数据范围:
1\leqslant n\leqslant 400,1\leqslant m\leqslant 10^5 。
分析
神仙题。
我们设
可以用一个倒推的dp来
- 枚举以后要存活的火鸡
i ,首先设边界f_{i,i}=1 (很显然)。 - 然后,我们倒序枚举每一个操作
(a,b) : -
- 如果
f_{i,a}=1 且f_{i,b}=1 ,那么意味着a 和b 以后一定要为i “抵命”,在这里不能死去,而这里有一次(a,b) 操作,因此a 和b 一定不能两个同时存活,那么i 以后一定活不了。
- 如果
-
- 如果
f_{i,a}=1 或f_{i,b}=1 ,那么不妨设f_{i,a}=1 (如果f_{i,b}=1 那么交换a,b ),因为a 以后要为i 抵命,所以b 一定得现在死来保护a 。
- 如果
-
- 如果
f_{i,a}=0 且f_{i,b}=0 ,那么操作(a,b) 与i 无关,不需要考虑。
- 如果
设
然后,我们枚举点对
题解
#include<stdio.h>
#include<bitset>
using namespace std;
const int maxn=405,maxm=100005;
int n,m,ans;
int a[maxm],b[maxm],no[maxn];
bitset<maxn>keep[maxn];
int main(){
freopen("c.in","r",stdin);
freopen("c.out","w",stdout);
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
scanf("%d%d",&a[i],&b[i]);
for(int i=1;i<=n;i++){
keep[i][i]=1;
for(int j=m;j>=1;j--){
if(keep[i][a[j]]&&keep[i][b[j]]){
no[i]=1;
break;
}
if(keep[i][a[j]])
keep[i][b[j]]=1;
else if(keep[i][b[j]])
keep[i][a[j]]=1;
}
}
for(int i=1;i<=n;i++)
for(int j=i+1;j<=n;j++)
if(no[i]==0&&no[j]==0&&(keep[i]&keep[j]).count()==0)
ans++;
printf("%d\n",ans);
return 0;
}