记
今天闲来无事,不想刷codeforces了,到洛谷提高组训练营找几道水题刷着玩玩(虽然自己早已过了打OI的年纪)~
简单博弈论专题
P1199 三国游戏
这么考虑,由于电脑总是不能让我搭配出当前能搭配出的最大的组合,也就是说我不管选哪个英雄,都不可能匹配出该英雄能搭配出的最大的默契值,那么我最大能搭配出的默契值就是所有英雄匹配中,次大默契值的最大值。
假设有
a、b、c、d
a
、
b
、
c
、
d
四位英雄
a
a
能匹配的最大值为次大值为
am
a
m
b
b
能匹配的最大值为次大值为
bm
b
m
c
c
能匹配的最大值为次大值为
cm
c
m
d
d
能匹配的最大值为次大值为
dm
d
m
假设其中 am>bm>cm>dm a m > b m > c m > d m
那么我能得到的最大可能值也就是
am
a
m
了。
下面证明,电脑能组合出的最大值一定比
am
a
m
要小。
那么我一开始选择
a
a
,电脑一定会选择与能形成
aM
a
M
的值的英雄,假设是
d
d
。
然后我再选,这里假设
c与a组合得到am
c
与
a
组
合
得
到
a
m
。
那么电脑会选择
b
b
。
反证法开始了:
假设
而
d与a形成的默契值为aM>am>dm
d
与
a
形
成
的
默
契
值
为
a
M
>
a
m
>
d
m
这样的话
dm
d
m
不可能是
d
d
能匹配到的次大值,矛盾。
所以
本次电脑不会选择到比
am
a
m
更大的数。
随后我们只需要破坏掉电脑构造成最大的组合就可以了。
P1288 取数游戏II
如果出发点靠着0边,那么只能往一个方向走,并且走过的边的值一定要减到0,不然对面会逆过来,然后把边减到0,这样我方就输了。
不靠0边的时候,如果沿着某个方向走会赢,那么就把沿路经过的边设置为0。
如果沿着那个方向走都不会赢,你任取一个方向走的时候不把边设置为0,对方在下一次走的时候也会继续沿着这个方向,并把下一条边设置成0,你还是要一直沿着这个方向走。
所以不管怎么样,经过的边都会变成0,也就是说一旦选定方向了,就只能一直走。
所以结果很固定,你只要把2个方向都老老实实沿着走下去,有一种情况能赢就能赢,否则就不能赢。
P1290 欧几里德的游戏
典型的辗转相除法。
假设这时候状态是
(a,b)满足a>b
(
a
,
b
)
满
足
a
>
b
如果
[a/b]=1
[
a
/
b
]
=
1
那么
(a,b)
(
a
,
b
)
与
(b,a%b)
(
b
,
a
%
b
)
的状态是相反的。
如果
[a/b]=k>1
[
a
/
b
]
=
k
>
1
那么就是必胜的,因为如果
(b,a%b)
(
b
,
a
%
b
)
是必败态,那么a直接剪去k个b,否则的话,a剪去k-1个b。很简单是不是。
P2148 [SDOI2009]E&D
山东的省选题,中规中矩。
由于多个组是独立游戏,所以Nim博弈,直接求出所有组的sg函数,然后异或就可以了,如果异或值为0则必败,否则必胜。
由于每个二元组
(a,b)
(
a
,
b
)
的范围都是
1e9
1
e
9
,这样的话,sg函数的复杂度太大了,所以要想想办法。
方法就是:打表找规律!2333
规律如下:
int sg(int x,int y){
if(x == 1 && y == 1)
return 0;
if(x%2 && y%2)
return 0;
if(x%2) swap(x,y);
if(y%2 == 0){
return sg(x/2,y/2)+1;
}
else{
return sg(x,y+1);
}
}
P1247 取火柴游戏
简单的不像话,跟上面的题难度一样,方法也一样,求sg函数,然后异或。
不同的地方是,这个要求第一步的方案。
在必胜态的时候,异或得到的值为ans。
我们第一步要做的就是拿掉一堆中的某些火柴,然后使异或值变为0。
假设我们要拿掉第i堆的某些火柴,那么这一堆应该剩下的火柴的数目就是
ans
a
n
s
xor
n[i]
n
[
i
]
,拿走的火柴数目就是
n[i]−
n
[
i
]
−
(
ans
a
n
s
xor
n[i]
n
[
i
]
)。
从小到达枚举堆数,找一个合法的情况就好了。
P2575 高手过招
这到也是sg函数➕异或。
不得不说sg函数是博弈论的大杀器呀。
求sg函数的方法就不说了,注意的点:
- 这题要用到位运算,压缩状态,不然时间、空间可能不够。
- 开O2!!!
代码
#include <iostream>
#include <cstdio>
#include <set>
#include <cstring>
#include <unordered_map>
using namespace std;
const int maxn = 2e6;
int T,n,m;
int sg[maxn];
int grundy(int status){
if(sg[status] != -1)
return sg[status];
unordered_map<int,int> mp;
for(int i = 19;i > 0;--i){
if(!(status&(1<<i))) continue;
int j = i-1;
for(;j >= 0;--j)
if(!(status&(1<<j)))
break;
if(j == -1) continue;
mp[grundy(status ^ ((1<<i) + (1<<j)))] = 1;;
}
for(int i = 0;;i++){
if(!mp[i])
return sg[status] = i;
}
}
int main(){
cin>>T;
memset(sg,-1,sizeof(sg));
while(T--){
scanf("%d",&n);
int ans = 0;
for(int i = 1;i <= n;++i){
scanf("%d",&m);
int status = 0;
for(int j = 1;j <= m;++j){
int p;
scanf("%d",&p);
status |= 1<<(20-p);
}
ans ^= grundy(status);
}
if(ans)
puts("YES");
else
puts("NO");
}
return 0;
}