bzoj3105 [cqoi2013]新Nim游戏

传统的Nim游戏是这样的:有一些火柴堆,每堆都有若干根火柴(不同堆的火柴数量可以不同)。两个游戏者轮流操作,每次可以选一个火柴堆拿走若干根火柴。可以只拿一根,也可以拿走整堆火柴,但不能同时从超过一堆火柴中拿。拿走最后一根火柴的游戏者胜利。

本题的游戏稍微有些不同:在第一个回合中,第一个游戏者可以直接拿走若干个整堆的火柴。可以一堆都不拿,但不可以全部拿走。第二回合也一样,第二个游戏者也有这样一次机会。从第三个回合(又轮到第一个游戏者)开始,规则和Nim游戏一样。

如果你先拿,怎样才能保证获胜?如果可以获胜的话,还要让第一回合拿的火柴总数尽量小。

【输入格式】

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

【输出格式】

输出第一回合拿的火柴数目的最小值。如果不能保证取胜,输出-1。

【输入输出样例】

nim.in

nim.out

6

5 5 6 6 5 5

21

nim.in

nim.out

3

1 2 3

1

 【数据范围】

编号

1-5

6-10

k

<=10

<=100

 

这个题bzoj上没贴图,于是我把题目粘上来了。

首先,这个题不是博弈论...

我们的目标是第一次拿尽量少的堆,剩下数目最多的堆,并且要保证这些堆无论对方怎么拿,都无法拿成必败态(异或和为0)。

*进一步转化就是说要选出一个尽量大的集合,使得这个集合的任意子集异或和都不为0。

这样我们可以贪心,每次拿最大的,判断是不是破坏以上性质。

下面我试着用拟阵来证明一下:

首先,遗传性:根据*,任意子集也满足性质。然后交换性:假设有2个子集A,B,|A|<|B|,一定存在x∈B-A使得A∪{x}满足性质。首先如果A是B的子集,那么A∪{x}也是B的子集,满足性质;如果A不是B的子集,那么B中至少存在2个不属于A的元素,那么如果这2个元素中有一个异或A的一个子集的异或和得0,那么另一个一定不得0,证毕。

那么就可以排序+贪心了。

验证是否破坏性质的时候需要用高斯消元判断一下已经拿的数是不是能够xor出要拿的数,如果无解,就说明这个数可以拿。最后没有拿的数的和就是答案了。由于最大的数一定可以被选,那么一定不会存在无解的情况。

 

nim
 1 #include<iostream>
 2 #include<cstdio>
 3 #include<algorithm>
 4 #include<cmath>
 5 #include<cstring>
 6 #define maxn 120
 7 #define bit 31
 8 #define inf 1000000000
 9 using namespace std;
10 long long a[maxn];
11 int v[maxn],c[maxn];
12 int n,m;
13 long long ans;
14  
15 bool gauss(int n,int m)
16 {
17     int k=1;
18     for (int i=1;i<=n;i++)
19     {
20         int p=0;
21         for (int j=k;j<=n;j++)
22             if (a[j]&(1<<(i-1))) {    p=j; break; }
23         if (p)
24         {
25             swap(a[p],a[k]);
26             for (int j=k+1;j<=n;j++)
27                 if (a[j]&(1<<(i-1))) a[j]^=a[k];
28             k++;
29         }
30     }
31     for (int i=1;i<=n;i++) if (a[i]==(1<<m)) return 0;//无解
32     return 1;//有解
33 }
34  
35 bool judge(int x)
36 {
37     for (int i=1;i<=m;i++) a[i]=0;
38     for (int i=1;i<=m;i++)
39         for (int j=1;j<=bit;j++)
40             if (v[i]&(1<<(j-1))) a[j]|=(1<<(i-1));
41     for (int j=1;j<=bit;j++)
42         if (x&(1<<(j-1))) a[j]|=(1<<m);
43     if (gauss(bit,m)) return 0; else return 1;
44 }
45  
46 bool cmp(int a,int b)
47 {
48     return a>b;
49 }
50 
51 int main()
52 {
53     scanf("%d",&n);
54     for (int i=1;i<=n;i++) scanf("%d",&c[i]);
55     sort(c+1,c+n+1,cmp);
56     v[m=1]=c[1];
57     for (int i=2;i<=n;i++)
58         if (judge(c[i])) v[++m]=c[i];
59         else ans+=c[i];
60     printf("%lld\n",ans);
61     return 0;
62 }

 

转载于:https://www.cnblogs.com/zig-zag/archive/2013/05/06/3063131.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值