记
今天闲来无事,不想刷codeforces了,到洛谷提高组训练营找几道水题刷着玩玩(虽然自己早已过了打OI的年纪)~
简单博弈论专题
P1199 三国游戏
这么考虑,由于电脑总是不能让我搭配出当前能搭配出的最大的组合,也就是说我不管选哪个英雄,都不可能匹配出该英雄能搭配出的最大的默契值,那么我最大能搭配出的默契值就是所有英雄匹配中,次大默契值的最大值。
假设有a、b、c、da、b、c、d四位英雄
aa能匹配的最大值为次大值为amam
bb能匹配的最大值为次大值为bmbm
cc能匹配的最大值为次大值为cmcm
dd能匹配的最大值为次大值为dmdm
假设其中am>bm>cm>dmam>bm>cm>dm
那么我能得到的最大可能值也就是amam了。
下面证明,电脑能组合出的最大值一定比amam要小。
那么我一开始选择aa,电脑一定会选择与能形成aMaM的值的英雄,假设是dd。
然后我再选,这里假设c与a组合得到amc与a组合得到am。
那么电脑会选择bb。
反证法开始了:
假设
而d与a形成的默契值为aM>am>dmd与a形成的默契值为aM>am>dm
这样的话dmdm不可能是dd能匹配到的次大值,矛盾。
所以
本次电脑不会选择到比amam更大的数。
随后我们只需要破坏掉电脑构造成最大的组合就可以了。
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)的范围都是1e91e9,这样的话,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堆的某些火柴,那么这一堆应该剩下的火柴的数目就是ansans xor n[i]n[i],拿走的火柴数目就是n[i]−n[i]− (ansans 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;
}