Poor turkey 时间倒流的反向思想以及bitset

题:Poor Turkeys

AtCoder 2389 洛谷:AT2389

题目大意:存在N只鸡,M个人,每个人轮流指定两只鸡,选择杀里面存活的一只(都死则不动),求最后可能存在的鸡对(i,j),1\leq i < j \leq nN \in [1,400] , M \in [1,100000]

分析:此题如果正向枚举会进入分叉还要结合其他条件判断,T = O(2^m)故不能直接正向做。但因为M很大N较小,那么我们就考虑复杂度更低的枚举答案,即枚举鸡对判断能否成立,而枚举的复杂度则为T = O(n^2 ),接下来我们就要考虑如何使鸡对成立,我们先从简单的分析一只鸡的存活条件开始。

单独分析鸡的存活条件

我们发现如果想要一个鸡 i 存活,如果它从来没有被指定,那肯定成立,但如果被指定过,那么就需要保证在指定的那一步时,杀掉另外一只鸡。这样我们就找到了”鸡的传火“——你想要一只鸡存活的条件是另一只鸡存活。然后我们又可以根据另一只鸡的条件进行判断......这样,我们最终根据这个传火就能O(m)判断 i 的存活条件,n只鸡在一起则为O(mn)并且由于是倒推存活,故为可称之为时间倒流。

我们接下来思考如何存储存活条件,由于直接涉及的对象是鸡,故我们考虑用一个bitset存储两只鸡间存活关系的影响,我们就考虑直接情况,即s[i][j]表示想要i存活需不需要j存活。

首先初始化,s[i][i] = 1即首先需要自己本身存活,然后举每个人的指认u,v,如果s[i][u] = 1那么s[i][v] = 1,此处也可以直接判断i的死活:如果s[i][u] = s[i][v] = 1,那么 i 今天biss

//a1[i],指认第一个,a2[i],指认第二个
for(int i = 1;i <= n; i++) s[i][i] = 1;
for(int i = 1;i <= n; i++)
{
    for(int j = m;j >= 1; j--)//时光倒流
	{
		if(s[i][a1[j]] and s[i][a2[j]])//s[i][j] = 1——要让j死保护i的情况,a1[j]和a2[j]如果都要保护i则不成立(其中一个一定会死)
		{
			dead[i] = 1;
			break;
		}
		if(s[i][a1[j]] or s[i][a2[j]])//只是其中一个要进行保护,则另一个也不能先死
			s[i][a1[j]] = s[i][a2[j]] = 1;
	}
}

 

分析鸡对的成立情况

接下来再回到鸡对的问题上,存在鸡对的情况为:

1 两只鸡本身都存活 即:dead[i] = dead[j] = 0;

2 两只鸡的存活set无交 即: s[i]&s[j] =0(s[i] \cap s[j] = 1)

最后n^2 枚举即可

for(int i = 1;i <= n; i++)
{
	if(dead[i]) continue;
	for(int j = i + 1;j <= n; j++)
	{
		if(dead[j]) continue;
		if(!(s[i]&s[j]).count())//交集为空则成立
			ans++;
	}
}

另:由于鄙人蒟蒻,故在此稍微总结一点bitset的用法

1 bitset 之间能进行进行& | ^,分别表示将两个bitset按位进行与,或,异或的操作

2 s.count()可以return 1 的个数

完整代码:

//9.05-9.16
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 100;
int n,m,a1[N],a2[N],d[501];
bitset<402>s[402];
int Read()
{
	int x = 0,k = 1;char ch = getchar();
	while(!isdigit(ch)) {if(ch == '-') k = -1;ch = getchar();}
	while(isdigit(ch)) {x = (x << 3) + (x << 1) + ch - '0';ch = getchar();}
	return x * k; 
}
int main()
{
	n = Read(),m = Read();
//	scanf("%d%d",&n,&m);
	for(int i = 1;i <= m; i++)
	{
		a1[i] = Read(),a2[i] = Read();
//		scanf("%d%d",&a1[i],&a2[i]);
//		s[a1[i]][a2[i]] = 1;
	}
	for(int i = 1;i <= n; i++) s[i][i] = 1;
	for(int i = 1;i <= n; i++)
	{
		for(int j = m;j >= 1; j--)
		{
			if(s[i][a1[j]] and s[i][a2[j]])
			{
				d[i] = 1;
				break;
			}
			if(s[i][a1[j]] or s[i][a2[j]])
			s[i][a1[j]] = s[i][a2[j]] = 1;
		}
	}
	long long ans = 0;
	for(int i = 1;i <= n; i++)
	{
		if(d[i]) continue;
		for(int j = i + 1;j <= n; j++)
		{
			if(d[j]) continue;
			if(!(s[i]&s[j]).count())
			ans++;
		}
	}
	cout << ans << '\n';
	return 0;
}

`

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值