33 数论 容斥原理和博弈论

/*
学acwing的算法基础课学来的,喜欢的话多多支持呀。
*/
/*容斥原理 给定一个整数 n 和 m 个不同的质数 p1,p2,…,pm。
请你求出 1~n 中能被 p1,p2,…,pm 中的至少一个数整除的整数有多少个*/
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=20;
int n,m;
int p[N];
int main()
{
   cin>>n>>m;
   for(int i=0;i<m;i++) cin>>p[i];//读入m个质数
   int res=0;//记录答案多少个
   for(int i=1;i<1<<m;i++)//二进制枚举二进制数的每一位,用来看第几位有1,也就是有多少个集合
   {
       int t=1,cnt=0;//t该位的数,cnt用来记录二进制1的个数
       for(int j=0;j<m;j++)//枚举位数
        if(i>>j&1)//假如第j位是1
       {
           cnt++;//位数加一
           if((ll)t*p[j]>n)//假如输入的质数大于n,则没答案
           {
               t=-1;
               break;
           }
           t*=p[j];//位数上有1的数的乘积
       }
       if(t!=-1)//假如输入正确
       {
           if(cnt&1) res+=n/t;//假如是奇数个位数,也就是奇数个集合,则符号是加上
           else res-=n/t;//假如是偶数个位数,也就是偶数个集合,则符号是减去上
       }
   }
   cout<<res<<endl;//输出最后答案
    return 0;
}
/*到达胜利之前无法回头*/

 2 博弈论

有两种状态 先手必胜和先手必败

1 拿任意棋子

/*
学acwing的算法基础课学来的,喜欢的话多多支持呀。
*/
//博弈论 拿棋子游戏 这里因为拿任意棋子看异或后的结果为不为0,假如是0则先手必败,反之先手必胜
#include<bits/stdc++.h>
using namespace std;
int main()
{
   int n;
   int res=0;
   scanf("%d",&n);
   while(n--)
   {
       int x;
       scanf("%d",&x);
       res^=x;//异或每一个数
   }
   if(res) puts("Yes");//假如异或后答案不为0,则先手必胜
   else puts("No");//反之先手必败
    return 0;
}
/*到达胜利之前无法回头*/

2 拿规定的棋子,规定的棋子有多种拿法 

/*
学acwing的算法基础课学来的,喜欢的话多多支持呀。
*/
//博弈论 拿棋子游戏 不是任意拿 规定拿数组中的多少个,有多种拿法
#include<bits/stdc++.h>
using namespace std;
const int N=1e4+10,M=1e2+10;
int n,m;//m是能拿的个数,n是石子的数
int s[M],f[N];//s存储的是可供选择的集合,f存储的是所有可能出现过的情况的sg值
int sg(int x)
{
    if(f[x]!=-1) return f[x]; //记忆化搜索 看是否被算过,算过直接放回数值即可,降低时间复杂度
    unordered_set<int> S;//用哈希表来存所有可以到的局面
    for(int i=0;i<m;i++)//进行m次操作
    {
        int sum=s[i];//当前能拿石子的个数
        if(x>=sum)//假如可以石子取
            S.insert(sg(x-sum));//把新的状态加进来,先延伸到终点的sg值后,再从后往前排查出所有数的sg值
    }
    for(int i=0;;i++)//判断集合中不存在的最小自然数是多少
        if(!S.count(i))//如果说当前这个数不存在的话
          return f[x]=i;//说明当前这个数的sg的值就是它
}
int main()
{
   cin>>m;
   for(int i=0;i<m;i++) cin>>s[i];//可供选择拿的数
   cin>>n;
   memset(f,-1,sizeof f);//方便记忆化 初始化f均为-1,方便在sg函数中查看x是否被记录过
   int res=0;
   for(int i=0;i<n;i++)//n堆石子
   {
       int x;
       scanf("%d",&x);
       res^=sg(x);//观察异或值的变化,基本原理与Nim游戏相同
   }
   if(res) puts("Yes");//假如异或后答案不为0,则先手必胜
   else puts("No");//反之先手必败
    return 0;
}
/*到达胜利之前无法回头*/

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值