数学(四):容斥原理、博弈论

容斥原理

原理:

 

 例题:能被整除的数

给定一个整数 n 和 m 个不同的质数 p1,p2,…,pm

请你求出 1∼n中能被 p1,p2,…,pm中的至少一个数整除的整数有多少个。

输入格式

第一行包含整数 n 和 m。

第二行包含 m 个质数。

输出格式

输出一个整数,表示满足条件的整数的个数。

数据范围

1≤m≤16
1≤n,pi≤109

输入样例:

10 2
2 3

输出样例:

7

解题:

 

 

 

#include <iostream>
#include <algorithm>

using namespace std;

typedef long long LL;

const int N=100010;

int n,m;
int p[N];
int main()
{
    cin>>n>>m;
    for(int i=0;i<m;i++) cin>>p[i];
    
    int res=0;//答案
    for(int i=1;i<1<<m;i++)//1<<m:2^m
    {
        int t=1,cnt=0;//t:所有质数的乘积,cnt:i里面有几个1,即几个集合
        for(int j=0;j<m;j++)
            if(i>>j&1)//当前位为1
            {
                cnt++;
                if((LL)t*p[j]>n)
                {
                    t=-1;
                    break;
                }
                t*=p[j];
            }
        if(t!=-1)
        {
            //判断有几个集合
            if(cnt%2) res+=n/t;//偶数是加上
            else res-=n/t;//奇数是减
        }
    }
    cout<<res<<endl;
    
    return 0;
}

博弈论

Nim游戏

给定 n 堆石子,两位玩家轮流操作,每次操作可以从任意一堆石子中拿走任意数量的石子(可以拿完,但不能不拿),最后无法进行操作的人视为失败。

问如果两人都采用最优策略,先手是否必胜。

输入格式

第一行包含整数 n。

第二行包含 n 个数字,其中第 i 个数字表示第 i 堆石子的数量。

输出格式

如果先手方必胜,则输出 Yes

否则,输出 No

数据范围

1≤n≤105
1≤每堆石子数≤109

输入样例:

2
2 3

输出样例:

Yes

解题:

 

/*

先手必胜状态:可以走到某一个必败状态
先手必败状态:走不到任何一个必败状态

*/

#include <iostream>
#include <algorithm>

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");
    else puts("No");
    
    return 0;
}

台阶-Nim游戏 

现在,有一个 n 级台阶的楼梯,每级台阶上都有若干个石子,其中第 i 级台阶上有 ai 个石子(i≥1)。

两位玩家轮流操作,每次操作可以从任意一级台阶上拿若干个石子放到下一级台阶中(不能不拿)。

已经拿到地面上的石子不能再拿,最后无法进行操作的人视为失败。

问如果两人都采用最优策略,先手是否必胜。

输入格式

第一行包含整数 n。

第二行包含 n 个整数,其中第 i个整数表示第 i级台阶上的石子数 ai。

输出格式

如果先手方必胜,则输出 Yes

否则,输出 No

数据范围

1≤n≤105
1≤ai≤109

输入样例:

3
2 1 3

输出样例:

Yes

 解题:

SG函数

Mex运算:设S表示一个非负集合,定义mex(S)为求出不属于集合S的最小非负整数的运算,即:

        mex(S)=min(x),x属于自然数,且x不属于S

原理:

 

 多个图的起点算SG(X)作异或,然后判断必败必胜态

 

 例题: 集合-Nim游戏

给定 n 堆石子以及一个由 k 个不同正整数构成的数字集合 S。

现在有两位玩家轮流操作,每次操作可以从任意一堆石子中拿取石子,每次拿取的石子数量必须包含于集合 S,最后无法进行操作的人视为失败。

问如果两人都采用最优策略,先手是否必胜。

输入格式

第一行包含整数 k,表示数字集合 S 中数字的个数。

第二行包含 k 个整数,其中第 i 个整数表示数字集合 S 中的第 i个数 si。

第三行包含整数 n。

第四行包含 n 个整数,其中第 i 个整数表示第 i 堆石子的数量 hi。

输出格式

如果先手方必胜,则输出 Yes

否则,输出 No

数据范围

1≤n,k≤100
1≤si,hi≤10000

输入样例:

2
2 5
3
2 4 7

输出样例:

Yes

解题:

 

 

#include <iostream>
#include <cstring>
#include <algorithm>
#include <unordered_set>

using namespace std;

const int N=110,M=10010;

int n,m;
int s[N],f[M];//s:表示石子个数,f:表示sg的值

int sg(int x)
{
    if(f[x]!=-1) return f[x];//被算过了就不重复计算了
    
    unordered_set<int> S;//用hash表存它所有可以到的局面
    for(int i=0;i<m;i++)
    {
        int sum=s[i];
        if(x>=sum) S.insert(sg(x-sum));
    }
    for(int i=0; ;i++)
        if(!S.count(i))
            return f[x]=i;
        
    
}

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

例题:拆分-Nim游戏

给定 n 堆石子,两位玩家轮流操作,每次操作可以取走其中的一堆石子,然后放入两堆规模更小的石子(新堆规模可以为 0,且两个新堆的石子总数可以大于取走的那堆石子数),最后无法进行操作的人视为失败。

问如果两人都采用最优策略,先手是否必胜。

输入格式

第一行包含整数 n。

第二行包含 n 个整数,其中第 i 个整数表示第 i 堆石子的数量 ai。

输出格式

如果先手方必胜,则输出 Yes

否则,输出 No

数据范围

1≤n,ai≤100

输入样例:

2
2 3

输出样例:

Yes

解题: 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值