保研机试训练Day-22

卡特兰数 && 容斥原理 && 博弈论

Posted by Jeffzzc on April 14, 2025

数论

卡特兰数

给定 n0n1,它们将按照某种顺序排成长度为 2n 的序列,求它们能排列成的所有序列中,能够满足任意前缀序列中 0 的个数都不少于 1 的个数的序列有多少个。

输出的答案对 10^9+7 取模。

#include<iostream>
#include<algorithm>
using namespace std;

typedef long long ll;
const int mod =  1e9+7;

int qmi(int a,int k,int p){
    int res = 1;
    while(k){
        if(k & 1) res = (ll)res * a % p;
        a = (ll)a * a % p;
        k >>= 1;
    }
    return res;
}

int main(){
    int n;
    cin >> n;
    int a = 2 * n, b = n;
    int res = 1;
  
    for(int i = a; i > a - b; i--) res = (ll)res * i % mod;
    for(int i=1;i<=b;i++) res = (ll)res * qmi(i, mod - 2, mod) % mod;
  
    res = (ll)res * qmi(n + 1, mod - 2, mod) % mod;
    cout << res << endl;
    return 0;
  
}

组合数学

容斥原理

给定一个整数 nm 个不同的质数 p1,p2,,pm。

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

#include<iostream>
#include<algorithm>
using namespace std;

typedef long long ll;
const int N = 20;
int n,m;
int p[N];

int main(){
    cin >> n >> m;
    int res = 0;
  
    for(int i = 0; i < m; i++) cin >> p[i];
  
    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 * 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 堆石子,两位玩家轮流操作,每次操作可以从任意一堆石子中拿走任意数量的石子(可以拿完,但不能不拿),最后无法进行操作的人视为失败。问如果两人都采用最优策略,先手是否必胜。

int main(){
    int n;
    scanf("%d", &n);
    int res = 0;
    for(int i = 0; i < n; i++) {
        int x;
        scanf("%d", &x);
        res ^= x;
    }
    if(res == 0) puts("No");
    else puts("Yes");
}

台阶-Nim游戏

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

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

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

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

#include<iostream>
using namespace std;

int main(){
    int n;
    cin >> n;
    int res = 0;
    for(int i = 1; i <= n; i++){
        int x;
        cin >> x;
        if(i % 2) res ^= x;
    }
    if(res) puts("Yes");
    else puts("No");
    return 0;
}

集合-Nim游戏

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

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

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

#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;
    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;
}