【NOI2015模拟9.9】取石子(博弈)

这里写图片描述

The Solution

个人觉得这种题要不就切掉要不就爆0了,所以我们要大胆地猜结论,然后去证明。(这也是一种思路吧)

因为一次只能取走一颗石子,因此对于所有石子,我们能进行的操作总数就是 s = 石子总数 + 石子堆数 - 1 .

我们可以感性地先猜一猜,如果考虑最简单的情况一堆的话,那么如果s是奇数那么很显然先手必胜,若是偶数那么先手必败。

那么我们拓宽一下思路,拓广到n堆石子。

首先数量为1的石子堆单独讨论,因此我们在统计时候不记录数量为1的石子堆对答案的贡献.

看到到数据很小,先考虑记忆化暴力。

我们设f[i][j]表示有i堆石子数目为1,能进行的操作数为j.
f[i][j]为1表示先手必胜,0表示先手必败.
这样的状态设计可以让我们把数量为1的石子和其他石子分开单独讨论. 这样我们就可以把数量为1的那些暴力出来.

然后在不考虑大小为1的石子堆的情况下,如果能够进行的操作数为奇数,则先手必胜,否则后手必胜.

证明如下:
如果只有1堆石子,该结论显然成立。
如果有多堆石子,每堆石子个数都大于1,并且s为偶数,下面我们证明这样先手必败。
1.如果先手选择合并两堆石子,那么每堆石子的个数依然大于1,s变为奇数。
2.如果先手选择从一堆石子数大于2的堆中拿走一枚石子,那么同上每堆石子个数依然大于1,s变为奇数。
3.如果先手选择从一堆石子数等于2的堆中拿走一枚石子,那么后手可以合并剩下的1枚石子到任意一个堆。
那样s的奇偶性不变,每堆石子的个数依然大于1.
综上所述,结论成立。
暴力时我们这样操作:
然后可进行的操作有以下几种:
1.取走一个大小为1的石子堆的石子.
2.取走某个大小不为1的石子堆的石子或者合并两个大小不为1的石子堆.
2.将一个大小为1的石子合并到另一个大小不为1的石子堆中.
3.合并两个大小为1的石子堆的石子.

这次题解我难得写那么长哎(〃'▽'〃)
~~或许是第一次做博弈切了比较有兴趣~~

CODE

#include <cstdio>
#include <iostream>
#include <cmath>
#include <algorithm>
#include <cstring>
#define fo(i,a,b) for (int i=a;i<=b;i++)
#define N 60
#define M (50 * 1000 + 5)

using namespace std;
int n,f[N][M];
bool Mark[N][M];

int read(int &n)
{
    char ch =' ';
    int q = 0,w = 1;
    for (;(ch != '-') && (ch < '0' || ch > '9');ch = getchar());
    if (ch == '-') w = -1,ch = getchar();
    for (;(ch >= '0' && ch <= '9');ch = getchar()) q = q * 10 + ch - 48;
    n = q * w;
    return n;
}

int Dfs(int x,int y)
{
    if (x == 0) return (y & 1);
    if (y == 1) return Dfs(x + 1,0);
    if (Mark[x][y]) return f[x][y];
    Mark[x][y] = true;
    if (x && ! Dfs(x - 1,y)) return (f[x][y] = 1);
    if (x && y && ! Dfs(x - 1,y + 1)) return (f[x][y] = 1);
    if (x >= 2 && ! Dfs(x - 2, y + 2 + (y ? 1 : 0))) return (f[x][y] = 1);
    if (y && ! Dfs(x,y - 1)) return (f[x][y] = 1);
    return (f[x][y] = 0);
}

int main()
{
    int T,tot;
    read(T);
    memset(f,-1,sizeof(f));
    while (T --)
    {
        read(n);
        int x,
        one = 0,many = 0;
        fo(i,1,n)
        {
            read(x);
            if (x == 1) one ++;
            else many += x + 1;
        }
        many --;
        many = many > 0 ? many : 0;
        printf(Dfs(one,many) ? "YES\n" : "NO\n");
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值