算法基础-数学知识-容斥原理、博弈论

容斥原理

在这里插入图片描述
容斥原理可以画一个韦恩图来看各个集合的关系

890. 能被整除的数(二进制状态压缩版本,复杂度多一个Om)

#include <iostream>
#include <algorithm>
#include <cmath>

using namespace std;
typedef long long LL;
const int N = 17;
int p[N];
void solve()
{
    
    
    int n, m;
    cin >> n >> m;
    for (int i = 0; i < m; ++ i) cin >> p[i];

    int res = 0;
    for (int i = 1; i < 1 << m; ++ i)//枚举2的n次方-1个集合
    {
    
    
        int t = 1, cnt = 0;
        for (int j = 0; j < m; ++ j)//判断是否乘上p[j]
        {
    
    
            if (i >> j & 1)
            {
    
    
                if ((LL)t * p[j] > n)
                {
    
    
                    t = -1;
                    break;
                }
                t *= p[j];
                cnt ++ ;
            }
        }
        if (t != -1)
        {
    
    
            if (cnt & 1) res += n / t;//奇数就加上,偶数就减去
            else res -= n / t;
        }
    }

    cout << res;
}
int32_t main()
{
    
    
    ios::sync_with_stdio(0);
    cin.tie(0);
    int T = 1;
    //cin >> T;
    while (T --) solve();
    return 0;
}

890. 能被整除的数(dfs版本)

本题本质上就是一个枚举所有答案的过程,那么我们当然可以用dfs搜索到所有可能的方案

#include <iostream>
#include <algorithm>
#include <cmath>

using namespace std;
typedef long long LL;
const int N = 17;
int p[N];
int n, m;
int res = 0;
void dfs(int u, int t, bool add)
{
    
    
    if (u == m)
    {
    
    
        if (t == 1) return ;
        else 
        {
    
    
            if (add) res += n / t;
            else res -= n / t;
            return;
        } 
    }
    dfs(u + 1, t, add);//m个质数中不选择p[i]
    if ((LL)t * p[u] <= n)//m个质数中不选择p[i]
    {
    
    
        dfs(u + 1, t * p[u], !add);//本层选了一个数的话,下一层枚举的集合的add就要取反,因为容斥原理公式就是1个的+,2个的-,3个的+,每多选一个数字,加减号相反
    }
}
void solve()
{
    
    
    cin >> n >> m;
    for (int i = 0; i < m; ++ i) cin >> p[i];

    dfs(0, 1, false);
    cout << res;
}
int32_t main()
{
    
    
    ios::sync_with_stdio(0);
    cin.tie(0);
    int T = 1;
    //cin >> T;
    while (T --) solve();
    return 0;
}

博弈论

在这里插入图片描述

  1. 为什么异或值=0先手必败?这个我没搞懂,我主要是记住这个结论
  2. 为什么异或值!=0先手必胜?
    因为它可以转化为对手先手必败的状态(异或值为0),推导如下
    在这里插入图片描述

无限制nim游戏

AcWing 891. Nim游戏

#include <iostream>
#include <algorithm>
#include <cmath>

using namespace std;
const int N = 1e5 + 10;
int a[N];
void solve()
{
    
    
   int n;
   cin >> n;
   for (int i = 0; i < n; ++ i) cin >> a[i];
   int t = 0;//异或运算里面的0相当于加法里面的0,乘法里面的1
   for (int i = 0; i < n; ++ i) 
   {
    
    
        t ^= a[i];
   }
   if (t) cout << "Yes" << endl;
   else cout << "No" << endl;
}
int32_t main()
{
    
    
    ios::sync_with_stdio(0);
    cin.tie(0);
    int T = 1;
    //cin >> T;
    while (T --) solve();
    return 0;
}

AcWing 892. 台阶-Nim游戏(待补)

集合版本Nim游戏

AcWing 893. 集合-Nim游戏

  1. 集合版本Nim游戏的每一步有多种选择,但多种选择是被限制在一个选择集合中的(而不是随意拿多少个)。
  2. sg(起点) != 0说明 我经过若干步一定可以到达 sg = 0的点,即我还是可以操作的,如果sg(起点) = 0,那我没有任何操作空间,直接判负,即先手必败。
  3. 推荐看这个博客
    在这里插入图片描述
#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <set>
using namespace std;
const int N = 1e4 + 10;
int s[N], f[N];//f可以不用,但可以起到剪枝的效果
int n, k;

int sg(int x)
{
    
    
    if (f[x] != -1) return f[x];
    
    set<int> S;
    for (int i = 0; i < k; ++ i)
        if(x >= s[i]) S.insert(sg(x - s[i]));//这里如果不判断x>=s[i]的话会影响后续路线的赋值,
                                        //本来下一层应该是1,2但是因为负数,变成了0, 1
                                        //那么本层本来是0的,递归回来的时候现在也会被影响变成了2
    
    for (int i = 0; i < k; ++ i)
    {
    
    
        if (S.count(i) == 0) return f[x] = i;
    }
}

void solve()
{
    
    
    memset(f, -1, sizeof f);
    cin >> k;
    for (int i = 0; i < k; ++ i) cin >> s[i];
    
    cin >> n;

    int x;
    int res = 0;
    while(n --)
    {
    
    
        cin >> x;
        res ^= sg(x);
    }

    if (res) cout << "Yes" << endl;
    else cout << "No" << endl; 

}
int32_t main()
{
    
    
    ios::sync_with_stdio(0);
    cin.tie(0);
    int T = 1;
    //cin >> T;
    while (T --) solve();
    return 0;
}

AcWing 894. 拆分-Nim游戏(待补)

猜你喜欢

转载自blog.csdn.net/chirou_/article/details/132722772