【BZOJ】【3759】Hungergame饥饿游戏

博弈论/高斯消元

  如果没有打开箱子这个操作,那么就是一个很裸的Nim游戏……

  但是有了打开箱子这个操作,就变得蛋疼了T_T

  首先我们可以想到一种直接的做法:打开所有箱子,当然如果此时所有a[i]的xor和==0则胜……

  但明显这样连样例也过不了╮(╯▽╰)╭

  那么我们可以想一下,对于一组全部没打开的箱子,我们进行一次操作后,会分成两组:打开的和没打开的(废话!)

  so sad……写不下去了……

  算了 Orz一下PoPoQQQ,搬运下题解:

先手必胜的状态为:给出的数字集合存在一个异或和为零的非空子集,则先手必胜

证明:

首先我们有状态A:当前的所有打开的箱子中的石子数异或和为零,且所有关闭的箱子中的石子数的集合中不存在一个异或和为零的非空子集

易证A状态时先手必败

先手有两种操作:

.从一个打开的箱子中拿走一些石子 那么根据Nim的结论 后手可以同样拿走一些石子使状态恢复为A状态

.打开一些箱子 由于未打开的箱子中不存在一个异或和为零的非空子集 所以打开后所有打开的箱子中石子数异或和必不为零 于是后手可以拿走一些石子使状态恢复为A状态

故此时先手必败

那么如果初始不存在一个异或和为零的非空子集,那么初始状态满足状态A,先手必败

如果初始存在一个异或和为零的非空子集,那么先手一定可以打开所有的异或和为零的子集,使剩余箱子不存在异或和为零的非空子集,将状态A留给后手,先手必胜

然后就是判断有没有异或和为零的非空子集的问题了……果断高斯消元求线性基

我的代码:(一开始直接暴力枚举的子集……)

 /**************************************************************
Problem: 3759
User: Tunix
Language: C++
Result: Accepted
Time:8280 ms
Memory:1272 kb
****************************************************************/ //BZOJ 3759
#include<cmath>
#include<vector>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#define rep(i,n) for(int i=0;i<n;++i)
#define F(i,j,n) for(int i=j;i<=n;++i)
#define D(i,j,n) for(int i=j;i>=n;--i)
#define pb push_back
using namespace std;
int getint(){
int v=,sign=; char ch=getchar();
while(!isdigit(ch)) {if(ch=='-') sign=-; ch=getchar();}
while(isdigit(ch)) {v=v*+ch-''; ch=getchar();}
return v*sign;
}
const int N=1e7+,INF=~0u>>;
const double eps=1e-;
/*******************template********************/ int a[];
int main(){
#ifndef ONLINE_JUDGE
freopen("input.txt","r",stdin);
// freopen("output.txt","w",stdout);
#endif
int T=getint();
while(T--){
int n=getint();
bool ans=;
rep(i,n) a[i]=getint(); F(i,,(<<n)-){
int tmp=,tmp2=;
rep(j,n)
if (i & (<<j)) tmp^=a[j];
if (tmp==){ ans=; break; }
}
puts(ans?"Yes":"No");
}
return ;
}

高斯消元:

 /**************************************************************
Problem: 3759
User: Tunix
Language: C++
Result: Accepted
Time:0 ms
Memory:1272 kb
****************************************************************/ //BZOJ 3759
#include<cmath>
#include<vector>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#define rep(i,n) for(int i=0;i<n;++i)
#define F(i,j,n) for(int i=j;i<=n;++i)
#define D(i,j,n) for(int i=j;i>=n;--i)
#define pb push_back
using namespace std;
int getint(){
int v=,sign=; char ch=getchar();
while(!isdigit(ch)) {if(ch=='-') sign=-; ch=getchar();}
while(isdigit(ch)) {v=v*+ch-''; ch=getchar();}
return v*sign;
}
const int N=1e7+,INF=~0u>>;
const double eps=1e-;
/*******************template********************/
int a[];
bool gauss(int n){
rep(i,n){
F(j,i+,n-) if (a[j]>a[i]) swap(a[i],a[j]);
if (!a[i]) return ;
D(j,,) if(a[i]>>j & ){
rep(x,n) if(x!=i && a[x]>>j&) a[x]^=a[i];
break;
}
}
return ;
}
int main(){
#ifndef ONLINE_JUDGE
freopen("input.txt","r",stdin);
// freopen("output.txt","w",stdout);
#endif
int T=getint();
while(T--){
int n=getint();
bool ans=;
rep(i,n) a[i]=getint();
puts(gauss(n)?"Yes":"No");
}
return ;
}

时间上差距也太大了!!!看来是出题人比较良心,不会高斯消元的童鞋也让过了……

3759: Hungergame

Time Limit: 10 Sec  Memory Limit: 512 MB
Submit: 120  Solved: 79
[Submit][Status][Discuss]

Description


于施惠国的统治极其残暴,每年从13个区中每个区中选出2名“贡品”参加饥饿游戏,而参加游戏的人必须在险恶的自然环境中杀死其余的人才能存活。游戏只会
有一个人活下来
凯特尼斯•伊夫狄恩和同区的皮塔•麦拉克在历经千难万阻后活了下来,然而残忍的游戏只允许一人存活,正当两人准备同时吃下有毒的果实自杀的时候,统治者被
打动了,他说:你们两个人跟我玩一个游戏,你赢了,我就让你们两个都活下来。女主角凯特尼斯•伊夫狄恩接受了挑战。
这个游戏是这样的,有n(n<=20)个箱子,每个箱子里面有ai(ai<=1000000000)个石头(怎么放进去的我就不知
道了),两个人轮流进行操作(女主角先手),每一次操作可以将任意个(大于0个)未打开的箱子打开(一开始所有的箱子都是关闭的),或者在已经打开的一个
箱子里拿走任意个(大于0个)石头(不能超过这个箱子现有的石头数)。最后谁无法操作谁就输了。
现在给出n,和这n个箱子里的石头数ai,女主角想知道她是否有绝对的把握取得胜利(很明显她的对手“统治者”是绝顶聪明的)。

Input

第一行有一个正整数T(表示有T组测试数据),对于每组测试数据有两行,第一行为一个正整数n,接下来有 n个数,第 i 个数表示ai.
 

Output

 有T行:对于每一个测试数据,如果先手可以必胜则输出“Yes”,否则输出“No”(没有引号)。

Sample Input

5
5
18 11 16 19 15
5
18 12 17 10 18
5
17 7 1 10 1
5
19 5 16 19 8
5
18 18 7 4 9

Sample Output

No
Yes
Yes
Yes
Yes

HINT

100%的数据:n<=20,T<=10,ai不超过1,000,000,000;

Source

[Submit][Status][Discuss]

上一篇:带你手写基于 Spring 的可插拔式 RPC 框架(一)介绍


下一篇:一个简单RPC框架是怎样炼成的(I)——开局篇