洛谷 P3185 [HNOI2007]分裂游戏 sg函数

题目描述

聪聪和睿睿最近迷上了一款叫做分裂的游戏。 该游戏的规则试: 共有 n 个瓶子, 标号为 0,1,2…..n-1, 第 i 个瓶子中装有 p[i]颗巧克力豆,两个人轮流取豆子,每一轮每人选择 3 个瓶子。标号为 i,j,k, 并要保证 i < j , j < = k 且第 i 个瓶子中至少要有 1 颗巧克力豆,随后这个人从第 i 个瓶子中拿走一颗豆 子并在 j,k 中各放入一粒豆子(j 可能等于 k) 。如果轮到某人而他无法按规则取豆子,那么他将输 掉比赛。胜利者可以拿走所有的巧克力豆! 两人最后决定由聪聪先取豆子,为了能够得到最终的巧克力豆,聪聪自然希望赢得比赛。他思考 了一下,发现在有的情况下,先拿的人一定有办法取胜,但是他不知道对于其他情况是否有必胜 策略,更不知道第一步该如何取。他决定偷偷请教聪明的你,希望你能告诉他,在给定每个瓶子 中的最初豆子数后是否能让自己得到所有巧克力豆,他还希望你告诉他第一步该如何取,并且为 了必胜,第一步有多少种取法? 假定 1 < n < = 21,p[i] < = 10000

输入输出格式

输入格式:
输入文件第一行是一个整数t表示测试数据的组数,接下来为t组测试数据(t<=10)。每组测试数据的第一行是瓶子的个数n,接下来的一行有n个由空格隔开的非负整数,表示每个瓶子中的豆子数。

输出格式:
对于每组测试数据,输出包括两行,第一行为用一个空格两两隔开的三个整数,表示要想赢得游戏,第一步应该选取的3个瓶子的编号i,j,k,如果有多组符合要求的解,那么输出字典序最小的一组。如果无论如何都无法赢得游戏,那么输出用一个空格两两隔开的三个-1。第二行表示要想确保赢得比赛,第一步有多少种不同的取法。

输入输出样例

输入样例#1:
2
4
1 0 1 5000
3
0 0 1
输出样例#1:
0 2 3
1
-1 -1 -1
0

分析:
好像这个叫做阶梯博弈。每一颗棋子都是独立的,可以回想之前那种往前跳 1 1 k步的棋子,也就是在同一位置每颗棋子都是一样的。我们先跑出每个位置的 sg s g 值,就是分裂后两个位置 sg s g 值的异或再取 mex m e x ,然后对于奇数的阶梯就累加上答案。对于输出方案数及方案,假设当前异或和为 l l ,取了i,j,k三个位置,那么就要 l xor i xor j xor k=0 l   x o r   i   x o r   j   x o r   k = 0 ,因为这三个位置奇偶性改变。

代码:

#include <iostream>
#include <cmath>
#include <cstdio>
#include <cstring>

using namespace std;

int test,n,m,ans;
int a[25],sg[25],l;
int h[10007];

int main()
{
    scanf("%d",&test);
    while (test--)
    {
        scanf("%d",&n);
        for (int i=1;i<=n;i++)
        {
            scanf("%d",&a[i]);
            sg[i]=0;
        }
        for (int i=n-1;i>0;i--)
        {
            memset(h,0,sizeof(h));
            for (int j=i+1;j<=n;j++)
            {
                for (int k=j;k<=n;k++)
                {
                    h[sg[j]^sg[k]]=1;
                }
            }
            for (int j=0;j<10000;j++)
            {
                if (!h[j])
                {
                    sg[i]=j;
                    break;
                }
            }
        }       
        l=0;        
        for (int i=1;i<=n;i++)
        {
            if (a[i]&1) l^=sg[i];
        }
        if (!l)
        {
            printf("-1 -1 -1\n0\n");
            continue;
        }       
        ans=0;
        for (int i=1;i<=n;i++)
        {
            if (!a[i]) continue;
            for (int j=i+1;j<=n;j++)
            {
                for (int k=j;k<=n;k++)
                {
                    if (!(l^sg[i]^sg[j]^sg[k]))
                    {
                        ans++;
                        if (ans==1) printf("%d %d %d\n",i-1,j-1,k-1);
                    }
                }
            }
        }
        printf("%d\n",ans);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值