BZOJ 1299: [LLH邀请赛]巧克力棒 【SG函数/博弈分析/高斯消元】

题目传送门

题目描述:

有n<=14根巧克力棒,每根有一个长度L<=109,开始时都在盒子里,每次操作可以把若干根巧克力棒从盒子里取出来,或者把一根已经取出来的巧克力棒吃掉正整数长度,最后不能操作者输,给定开始每根巧克力棒的长度,问先手是否必胜。

题目分析:
法一:非常暴力且通用的 O ( 3 n ) O(3^n) O(3n)算法

直接枚举后继算出SG值
f [ s ] f[s] f[s]表示还有s状态的巧克力棒未取出的SG值, g [ s ] g[s] g[s]表示s状态的巧克力棒的nim和,那么
f [ s ] = m e x { f [ t ]   x o r   g [ s − t ] } f[s]=mex\{f[t]~xor~g[s-t]\} f[s]=mex{f[t] xor g[st]}

法二:分析平衡状态, O ( 2 n ) O(2^n) O(2n)判断

假设有m根巧克力棒,它们长度的异或和为0(也就是nim游戏的必败态)
且剩下的n-m根巧克力棒的任意组合异或和都不为0。
那么先手只需要把这m根巧克力棒取出盒子,把nim游戏的必败态留给对方,如果对方又从盒子里取出一些巧克力棒,那么这些巧克力棒的nim和肯定不为0,先手只需要再把这些巧克力棒取成必败态即可,这样就可以做到始终把必败态留给对方。
也就是说,如果存在nim和为0的极大巧克力棒组合,那么先手必胜;
如果任何巧克力棒的组合nim和都不为0,那么先手必败。
O ( 2 n ) O(2^n) O(2n)看是否有异或和为0的组合即可。

法三:找到结论之后, O ( n 2 ∗ l o g L ) O(n^2*logL) O(n2logL)高斯消元判断

根据法二的分析,我们并不需要枚举所有巧克力棒选或不选。
把第 i i i根巧克力棒的状态看成一个变量 x i x_i xi,把长度 L i Li Li拆成二进制 a i 1 , a i 2 . . . , a i k a_{i1},a_{i2}...,a_{ik} ai1,ai2...,aik,那么就是这样的一个方程组:
{ a 11 x 1   x o r   a 21 x 2   . . .   x o r   a n 1 x n = 0 a 12 x 1   x o r   a 22 x 2   . . .   x o r   a n 2 x n = 0 ⋯ a 1 k x 1   x o r   a 2 k x 2   . . .   x o r   a n k x n = 0 \left\{ \begin{aligned} &amp;a_{11}x_1~xor~a_{21}x_2~...~xor~a_{n1}x_n=0\\ &amp;a_{12}x_1~xor~a_{22}x_2~...~xor~a_{n2}x_n=0\\ &amp;\cdots\\ &amp;a_{1k}x_1~xor~a_{2k}x_2~...~xor~a_{nk}x_n=0 \end{aligned} \right. a11x1 xor a21x2 ... xor an1xn=0a12x1 xor a22x2 ... xor an2xn=0a1kx1 xor a2kx2 ... xor ankxn=0
上面这个方程组的解的数量就是异或和为0的巧克力棒组合。
因为 x i x_i xi要么为1要么为0,所以方程组的解的数量就是 2 自 由 元 个 数 2^{自由元个数} 2
由于每个x都为0显然是一组解,所以至少有一个自由元代表存在一个不为空的巧克力棒组合异或和为0,只要看是否有自由元即可。
写代码的时候k直接取到30就好了,时间复杂度 O ( n 2 ∗ 30 ) O(n^2*30) O(n230)

这里只贴出高斯消元的代码~:

#include<cstdio>
#include<algorithm>
using namespace std;
int n;
bool a[35][15];
bool Gauss(const int N){
    for(int i=1;i<=n;i++){//n个变量
        if(!a[i][i]) for(int j=i+1;j<=N;j++) if(a[j][i]) swap(a[i],a[j]);
        if(!a[i][i]) return 1;//存在自由元
        for(int j=i+1;j<=N;j++)
            if(a[j][i]) for(int k=i;k<=n;k++) a[j][k]^=a[i][k];
    }
    return 0;
}
int main()
{
    for(int t=1;t<=10;t++){
        scanf("%d",&n);
        for(int i=1,x,j;i<=n;i++)
            for(scanf("%d",&x),j=0;j<30;j++) a[j+1][i]=x>>j&1;
        puts(Gauss(30)?"NO":"YES");
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值