【算法基础19】容斥原理与简单博弈论(Nim游戏)

一、容斥原理

        主要思想:通过韦恩图找规律,总结出求n个集合的并集的公式。

       

        例题:给出整数n和m个质数,求出在1-n中能够被至少一个质数整除的数有多少个。

#include<iostream>
using namespace std;

typedef long long LL;
const int N=1010;
int n,m;
int primes[N];

int main(){
	cin>>n>>m;
	for(int i=0;i<m;i++) cin>>primes[i];
	
	int res=0;
	for(int i=1;i<1<<m;i++){//二进制表示某个数是否被选中,详见注释
		int t=1,cnt=0;
		for(int j=0;j<m;j++)//遍历给定质数
			if(i>>j&1){//当前质数被选中
				cnt++;
				if((LL)t*primes[j]>n){//超出题目范围,不用管
					t=-1;
					break;
				}
				t*=primes[j];//用t算多个数的倍数,对应韦恩图中多个集合的并集
			}
			
		if(t!=-1){
			if(cnt%2) res+=n/t;//偶数项为加号
			else res-=n/t;//奇数项为减号
		}		
	}
	
	cout<<res<<endl;
	
	return 0;
}

        注:n个数有2^{n}-1种取法,将n用二进制表示,第 i 位为1即表示数 i 被取到,遍历2^{n}-1

次即可以涵盖所有数的所有取法。 

                        ​​​​​​​        ​​​​​​​        ​​​​​​​ 

二、简单博弈论

        公平组合游戏:两名玩家交替行动,可执行的行动与先后手无关,不能行动判负。
        

        1.Nim游戏

        题目要求:给n堆石子,两名玩家依次拿取任意个数的石子,最后无法操作的为失败。若玩家都采用最优策略,请判断先手是必胜还是必败。

        问题分析:将n堆石子数量a1,a2...an相异或,如果结果是0,则当前总数可以均分成两堆,玩家取任意个数石子,对手都可以镜像地取同样多个数石子,先手必败;如果结果是x,则在n堆中必有一堆的数量ai在x的二进制表示最高位(第k位)为1,由于 ai^x < ai 玩家只要取 ai - (ai ^ x) 个石子,第 i 堆数量就由 ai 变为 ai ^ x ,n堆石子数量异或变为 x ^ x ,结果为0,先手必胜。 

        代码:

#include<iostream>
using namespace std;

int main(){
	int n,res=0;
	cin>>n;
	
	while(n--){
		int x;
		cin>>x;
		res^=x;//进行异或
	}
	if(res) cout<<"yes"<<endl;
	else cout<<"no"<<endl;
	
	return 0;
}

        2.集合-Nim游戏

        题目要求:在Nim游戏的基础上给定一个由k个不同正整数组成的集合S,玩家每次只能取S中包含的数的个数个石子,在双方都采用最优策略的情况下,判断先手是否必胜。

        主要思想:SG函数为没有出现在可取集合中的最小正整数终点的SG函数为0。对于每一堆石子,每次取k个不同数量的石子会导致k种不同情况,形成一个有向无环图。由终点的SG为0可以逆推出起点的SG值,假设石子只有当前这一堆,则同Nim游戏证明过程,若起点SG函数为0则先手必败,若不为0则先手必胜。扩展到n堆石子问题,将n堆石子数量的SG函数异或,结果为0则先手必败,否则先手必胜。

 

        ​​​​​​​        ​​​​​​​     

 

        代码:

#include<iostream>
#include<cstring>
#include<unordered_set>
using namespace std;

const int N=1010,M=1010;
int s[N],f[M];//s数组存每次可取个数,f数组存每堆石子个数对应的sg函数
int n,m;

int sg(int x){
	if(f[x]!=-1) return f[x];//避免相同个数的石子堆SG重复计算
	
	unordered_set<int> S;	
	for(int i=0;i<m;i++){
		int sum=s[i];
		if(x>=sum) S.insert(sg(x-sum));//将取了s[i]导致的情况添加到图中,注意此处递归
	}
	
	for(int i=0;;i++){//求SG函数值,count函数为i出现的次数
		if(!S.count(i)) return f[x]=i;
	}
}

int main(){
	cin>>n;
	for(int i=0;i<n;i++) cin>>s[i];
	
	cin>>m;
	memset(f,-1,sizeof f);
	int res=0;
	
	while(m--){
		int x;
		cin>>x;
		res^=sg(x);
	}
	
	if(res) puts("yes");
	else puts("no");
	
	return 0;
}

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值