BZOJ3105 新Nim游戏(NIM游戏,线性基)

传统的Nim游戏是这样的:有一些火柴堆,每堆都有若干根火柴(不同堆的火柴数量可以不同)。两个游戏者轮流操作,每次可以选一个火柴堆拿走若干根火柴。可以只拿一根,也可以拿走整堆火柴,但不能同时从超过一堆火柴中拿。拿走最后一根火柴的游戏者胜利。
本题的游戏稍微有些不同:在第一个回合中,第一个游戏者可以直接拿走若干个整堆的火柴。可以一堆都不拿,但不可以全部拿走。第二回合也一样,第二个游戏者也有这样一次机会。从第三个回合(又轮到第一个游戏者)开始,规则和Nim游戏一样。
如果你先拿,怎样才能保证获胜?如果可以获胜的话,还要让第一回合拿的火柴总数尽量小。

Input
第一行为整数k。即火柴堆数。第二行包含k个不超过109的正整数,即各堆的火柴个数。

Output

输出第一回合拿的火柴数目的最小值。如果不能保证取胜,输出-1。
Sample Input
6
5 5 6 6 5 5
Sample Output
21
Hint
k<=100

思路:
我们知道NIM博弈的必胜态是异或和不为0。
对于数集,线性基是异或和不为0且数目最少的子集。
只要先手取完剩下的是线性基,非线性基部分就是先手取的。那么后手取完,剩下的数目异或和一定不为0了,那么先手进行NIM游戏的时候就是必胜态了。

因为题目要求最小,那么从大到小开始插入。

#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

typedef long long ll;
int a[105],d[105];

int insert(int x)
{
    for(int i = 31;i >= 0;i--)
    {
        if((x >> i) & 1)
        {
            if(d[i])x ^= d[i];
            else
            {
                d[i] = x;
                return true;
            }
        }
    }
    return false;
}

int main()
{
    int n;scanf("%d",&n);
    ll res = 0;
    for(int i = 1;i <= n;i++)
    {
        scanf("%d",&a[i]);
    }
    sort(a + 1,a + 1 + n);
    for(int i = n;i >= 1;i--)
    {
        if(!insert(a[i]))res += a[i];
    }
    printf("%lld\n",res);
    return 0;
}

发布了628 篇原创文章 · 获赞 17 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/tomjobs/article/details/104016193