第四章 数学知识(四)——容斥原理与博弈论

文章介绍了容斥原理在计算相交集合元素个数中的应用,以及在博弈论中的Nim游戏策略。容斥原理通过加减交集元素个数来确定并集大小,而博弈论中的Nim游戏展示了先手优势和必胜/必败态的判断。此外,还提到了SG函数在有限操作石子游戏中判断胜负的角色,以及如何通过递归和记忆化搜索计算SG值。
摘要由CSDN通过智能技术生成

容斥原理

若干个相交集合,它们的并集中存在多少个元素?
假设n为所有集合的元素个数相加,因为集合间存在交集,所以n中有重复计算
比如三个集合 A , B , C A,B,C ABC
∣ A ∪ B ∪ C ∣ = ∣ A ∣ + ∣ B ∣ + ∣ C ∣ − ∣ A ∩ B ∣ − ∣ A ∩ C ∣ − ∣ B ∩ C ∣ + ∣ A ∩ B ∩ C ∣ |A∪B∪C| = |A| + |B| + |C| - |A∩B| - |A∩C| - |B∩C| + |A∩B∩C| ABC=A+B+CABACBC+ABC
推广到n个集合,并集的元素个数 = 每个集合的元素个数相加 - 任意两个集合的交集元素个数 + 任意三个集合的交集元素个数 - …
为什么要这样的重复?因为式子的每一项都有重复操作,如:每个集合的元素个数相加,因为任意两个集合间可能存在交集,所以有些元素被二次增加
减去这些被二次增加的元素,因为任意三个集合间可能存在交集,所以有些元素被二次删除
加上这些被二次删除的元素,可以发现每个操作都在弥补上次操作的重复
何时停止?加上或减去所有集合的交集时停止

那么推广公式中有多少项?
观察所有项,有些项是一个集合的交集(本身)元素个数,有些项是两个,有些是三个,最多到n个。所以这是个组合问题,从n个集合中选择k个集合,k从1~n,将这些组合数相加就是项数
image.png
根据等式:
∑ k = 0 n C n k = 2 n \sum_{k=0}^{n}C_n^k=2^n k=0nCnk=2n
所以推得:项数 = 2 n 2^n 2n - 1

容斥原理证明:
假设在 n n n个集合中,数 x x x属于 k k k个不同的集合,在容斥原理的展开式中 x x x的出现次数为(蓝字):
image.png
列出该表达式,根据等式:
∑ i = 1 k ( − 1 ) i − 1 C k i = 1 \sum_{i = 1}^{k}(-1)^{i-1}C_k^i=1 i=1k(1)i1Cki=1
所以在容斥原理的展开式中, x x x的出现次数为1,即在所有集合的并集中, x x x只出现一次
x x x推广为集合中的任意元素,即证:容斥原理成立


博弈论

先手不是指第一出手方,而是指下一出手方
必胜态:可以走到(下一回动手)先手必败态
必败态:走不到先手必败态,不论怎么走都将走到先手必胜态

问题:
image.png

假设n堆石子的数量为 a 1 , a 2 , . . . , a n a_1, a_2, ..., a_n a1,a2,...,an,若a_1 ^ a_2 ^ ... ^ a_n = 0,则先手必败
否则先手必胜

分析:

  1. 不能进行任何操作(所有堆的石子为0),异或结果为0
  2. 异或结果不是0,一定可以通过某种方式使得异或结果为0

当异或结果非0时,一定有方法使得拿走石子后,异或结果为0
证明:
当异或结果为x(非0),x的二进制表示中,最高的一位1在第k位
其他数中必然存在至少一个数 a i a_i ai,其第k位为1
那么就有 a i a_i ai ^ x < a i a_i ai a i a_i ai ^ x 后,第k为从1变为0,结果减小
从堆中拿走( a i a_i ai - a i a_i ai ^ x)的石子,那么堆中剩下的石子数量为 a i a_i ai-( a i a_i ai - a i a_i ai ^ x)=( a i a_i ai ^ x)
此时将所有堆的石子数量进行异或,结果为0,因为只有 a i a_i ai变为了 a i a_i ai ^ x
其中x为 a i a_i ai变化之前的异或结果, a i a_i ai变化后,异或结果为x ^ x = 0

异或结果为0时,无论怎么拿,之后的异或结果一定不是0
证明:反证
假设从 a i a_i ai中拿走石子,剩下 a i ′ a_i' ai的石子,若此时的异或结果为0,将此时的异或等式与之前的异或等式进行异或
那么除了 a i a_i ai a i ′ a_i' ai剩下的项都是相同的,因为异或结果为0,所以 a i a_i ai ^ a i ′ a_i' ai = 0
说明 a i a_i ai = a i ′ a_i' ai,也就是没有拿走石子,与前提 a i ′ a_i' ai < a i a_i ai矛盾
所以若异或等式为0,那么无论此时怎么拿,异或结果不是0

因此,若双方都尽力在赢,那么双方就能使异或结果非0的局面转换到异或结果为0,也能使异或结果为0的局面转换为非0
当没有石子剩下,此时异或结果为0
所以,先手方的局面中,异或结果为0,那么先手必败。反之,先手必胜


SG函数

若题目不再允许每次拿走任意石子,而限定每次拿走的石子数量时
以上结论依然有效,只不过用来做异或运算的不再是每堆石子的数量,而是SG值:
先手时,SG(x) == 0为必败态,SG(x) != 0为必胜态
将n堆石子能进行的所有局面看成一张图,将每张图中起点SG的值异或,结果为0必败,否则必胜

如何计算SG值?
每进行一个操作都能从一个局面转移到另一个局面,用点表示局面,对于初始状态的所有可能操作就组成了一张有向图

若一个点没有出边,那么该点就被定义为终点,SG(终点) = 0
对于当前点,也就是,当前局面x,若其能转移成 y 1 y_1 y1, y 2 y_2 y2, …, y k y_k yk局面,那么:
SG(x) = Mex(SG( y 1 y_1 y1), SG( y 2 y_2 y2), … , SG( y k y_k yk))
定义Mex运算,找到一个集合中不存在的最小的非负整数,也就是说在集合中,小于该数的所有非负整数是存在且连续的
也就是说,若SG(x) = n,那么从当前局面x一定能够转移到SG值小于n的任意局面

给定一个初始集合,即已知起点局面,此时如何知道其他局面?即如何推导有向图中其他点的SG值?
和扩展欧的递归求系数类似,我们需要递归到不能递归为止,以找到了终点,由于已知终点的SG值为0,所以此时可以更新终点
通过终点的SG值倒推其他点的SG值:若一点只能通向终点,那么该点的SG值为1
推广:若已知一点连通的其他点,我们需要从0开始枚举这些点的SG值,找一个不存在且最小的值,作为当前点的SG值
出边连通的点的SG值用set存储,这样就不用排序了

用石子的数量表示图中的一个唯一点,用其查询该点的SG值
如下图,若某个石子堆中出现了剩余石子数量相同的局面(两个剩余石子数量为3的局面),因为之后能转移的局面是相同的,所以这两个局面可以合并,作为图中的一个唯一点(对于不同的石子堆也是如此)
由此可知,一张图中可能存在多个相同的点(石子数量相同的局面),为防止重复查询降低效率,这里使用记忆化搜索。用数组f记录剩余石子数量为i时,该局面的SG值

image.png

模板:

int cnt[M]; // 每次能拿走的石子数量(能进行的操作)
int f[N];
memset(f, -1, sizeof(f));
int sg(int x) // x为堆中剩余的石子数量,函数返回该点的SG值
{
	if (f[x] != -1) return f[x];

	unordered_set<int> s; // 当前点的出边连通的点的SG值
	for (int i = 0; i < m; ++ i ) // m为堆的数量
		if (x >= cnt[i]) s.insert(sg(x - cnt[i])) ;
	for (int i = 0; ; i ++ )
		if (!s.count(i)) return f[x] = i;
}

为什么能从0->!0,!0->0?证明的思路和原题一样,
!0->0:
异或结果为x时,因为Mex的存在, a i a_i ai一定能变化成 a i a_i ai ^ x,因为 a i a_i ai ^ x < a i a_i ai
0->!0:
用反证法,证明过程与原题相同


容斥原理练习题

890. 能被整除的数

890. 能被整除的数 - AcWing题库
image.png

暴力做法:判断1~n中的每个数是否能被质数整除,最坏的情况每个数要判断m次,总共nm次

容斥原理:
每个集合定义为:在1~n中,质数 p i p_i pi的倍数的个数(能被 p i p_i pi整除的数的个数),用下取整 [ n / p i ] [n/p_i] [n/pi]即可求得
问题转换成了:求这些集合的并集中元素的个数

首先,容斥原理展开式有 2 n 2^n 2n-1项, n n n为集合的数量
因为题目给定的质数个数最为16,也就是 n n n最多为16
对于展开式中的某一项,我们可以直接对一个数的第 i i i位进行位运算判断第 i i i集合是否在该项中(作为并集的一部分)
由于每一项至少要选择一个集合,所以数从1开始
若该项有奇数个集合,加上该项,否则减去该项

#include <iostream>
using namespace std;

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

int main()
{
    scanf("%d%d", &n, &m);
    for (int i = 0; i < m; ++ i ) scanf("%d", &p[i]);
    int res = 0;
    for (int i = 1; i < 1 << m; ++ i )
    {
        int t = 1, s = 0; // t为分母,s为分母中集合的个数
        for (int j = 0; j < m; ++ j )
        {
            if ((i >> j) & 1)
            {
                if ((LL)t * p[j] > n)
                {
                    s = -1;
                    break;
                }
                t = (LL)t * p[j];
                s ++ ;
            }
        }
        if (s == -1) continue;
        if (s % 2) res += n / t;
        else res -= n / t;
    }
    printf("%d\n", res);
    
    return 0;
}

博弈论练习题

891. Nim游戏

891. Nim游戏 - AcWing题库
image.png

#include <iostream>
using namespace std;

int main()
{
    int n;
    scanf("%d", &n);
    int res = 0, x;
    while (n -- )
    {
        scanf("%d", &x);
        res ^= x;
    }
    
    if (res) puts("Yes");
    else puts("No");
    
    return 0;
}

893. 集合-Nim游戏

893. 集合-Nim游戏 - AcWing题库
image.png

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

const int K = 110, M = 10010;
int cnt[K], f[M];
int k, n;

int sg(int x)
{
    if (f[x] != -1) return f[x];
    unordered_set<int> s;
    for (int i = 0; i < k; ++ i )
        if (x >= cnt[i]) s.insert(sg(x - cnt[i]));
        
    for (int i = 0; ; ++ i )
        if (!s.count(i)) return f[x] = i;
}

int main()
{
    scanf("%d", &k);
    for (int i = 0; i < k; ++ i ) scanf("%d", &cnt[i]);
    
    scanf("%d", &n);
    memset(f, -1, sizeof(f));
    int x, res = 0;
    while (n -- )
    {
        scanf("%d", &x);
        res ^= sg(x);
    }
    
    if (res) puts("Yes");
    else puts("No");
    
    return 0;
}

892. 台阶-Nim游戏

892. 台阶-Nim游戏 - AcWing题库
image.png

将所有奇数台阶看成经典Nim游戏即可
当所有奇数台阶的石子异或结果为0,先手必败。反之先手必胜
证明:
必败的局面为:所有奇数台阶的石子不为0,偶数台阶的石子为0,此时只能从奇数台阶拿石子到偶数台阶,但是对方能拿走你拿下的石子。也就是对方始终能保证偶数台阶的石子为0,并且自己的局面中,一定有奇数台阶的石子不为0
最后的情况为:1号台阶的石子不为0,其他台阶的石子为0,此时先手方必胜

推广一下,保证所有奇数台阶的石子数量异或结果为0,那么一定能递达一个局面,即所有奇数台阶的石子数量为0。此时先手必败

对于所有奇数台阶的石子异或结果为0的局面,先手方:

  1. 从偶数台阶拿下石子,后手方拿走先手方拿下的石子即可,将变化的奇数台阶数量恢复
  2. 从奇数台阶拿下石子,后手方一定能从某个奇数台阶拿走石子,使得奇数台阶的石子数量异或结果为0。这是经典的Nim游戏
#include <iostream>
using namespace std;

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

894. 拆分-Nim游戏

894. 拆分-Nim游戏 - AcWing题库
image.png

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

const int N = 110;
int n, f[N];

int sg(int x)
{
    if (f[x] != -1) return f[x];
    unordered_set<int> s;
    for (int i = 0; i < x; ++ i )
        for (int j = 0; j <= i; ++ j)
            s.insert(sg(i) ^ sg(j));
    for (int i = 0; ; ++ i )
        if (!s.count(i))
            return f[x] = i;
}

int main()
{
    memset(f, -1, sizeof(f));
    scanf("%d", &n);
    int res = 0, x;
    while (n -- )
    {
        scanf("%d", &x);
        res ^= sg(x);
    }
    
    if (res) puts("Yes");
    else puts("No");
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值