学习(复习)博弈已经两天,然而看和思考的题远远不够,没有抓紧时间
学习一个模型最重要的是能够掌握其基本的定理和运用,需要尽可能多的看各类的题。要抓紧时间找题来想,深入的思考,不能懒惰!
基础的博弈理论
zwfymqz的总结
yyb的总结
**两位大佬总结的非常详细了,我就不再赘述了
博弈最重要的是从博弈的本质出发,能够运用和转化模型:
根据N,P状态的定义来寻找性质
多个子游戏的SG抑或起来,那么我们需要化简出最简的子游戏,通过DP,搜索或者打表来求出SG函数
通过两轮后性质的不变来简化模型----两轮后仍具有的相同性质说明这两轮不会影响胜负(比如取模)
二分图博弈的性质要注意:
点不重的二分图博弈:如果起点在所有最大匹配上则先手必胜。
每次删边的二分图博弈:如果能够找到右边的集合,与左边的相邻集合只有起点度数为奇数,则先手必胜
anti-SG
注意不是所有最后一个操作的人输的题目都是anti-SG。要满足:
对于任意一个Anti-SG游戏,如果我们规定当局面中所有的单一游戏的SG值为0时,游戏结束。(该条件可以弱化为:当局面中所有的单一游戏的SG值为0时,存在一个单一游戏它的SG函数能通过一次操作变为1)
先手必胜当且仅当:
(1)游戏的SG函数不为0且游戏中某个单一游戏的SG函数大于1;
(2)游戏的SG函数为0且游戏中没有单一游戏的SG函数大于1。
例如在取石子(最经典的nim)中,单一游戏SG = 0当且仅当石子数为空,游戏显然结束。
证明看2009 贾志豪的论文《组合游戏略述 ——浅谈SG游戏的若干拓展及变形》
无向图删边游戏:
缩边双:边双里有偶数条边,则SG = 0 , 奇数条边,则SG = 1
然后在树上做删边游戏
例题和好题
lzw’s blog
这上面有很多opentrains上的题,考虑到以后可能会训练,就没有看
anti-SG 例题:树上删边,最后一个删的人输
首先这是满足anti-SG的条件的。因为删边本质是取石子的组合
还要注意在删边游戏中每个子游戏是根下面的儿子,即一个根代表度数个子游戏的组合,而子游戏与根无关
这题需要输出方案,开始想得特别麻烦,以为要自底向上维护删除的方案。然而,从SG的定义考虑,一个点的SG = i意味着它可以转移到
0
−
(
S
G
−
1
)
0 - (SG - 1)
0−(SG−1)的所有局面。所以直接dfs即可,一定有解。
按照SJ定理讨论清楚,所有情况
当输出了一组合法方案的时候直接exit(0),这样写最简单。因为输出了多组方案调了一会。
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 10;
struct node{
int next,to;
}e[maxn * 2];
int head[maxn],cnt,f[maxn],n,m,deg[maxn],sz[maxn],fa[maxn];
vector <int> vec,vec2;
void adde(int x,int y){
e[++cnt].to = y;
e[cnt].next = head[x];
head[x] = cnt;
}
void dfs(int x){
sz[x] = 1;
for (int i = head[x] ; i ; i = e[i].next){
fa[e[i].to] = x;
dfs(e[i].to);
f[x] ^= f[e[i].to] + 1;
sz[x] += sz[e[i].to];
}
}
void dfs_ans(int x,int SG){
if ( SG == -1 ){
printf("%d %d\n",fa[x],x);
exit(0);
}
for (int i = head[x] ; i ; i = e[i].next){
int cur = (SG ^ f[x] ^ (f[e[i].to] + 1)) - 1;
if ( cur < f[e[i].to] ){
dfs_ans(e[i].to,cur);
return;
}
}
assert(0);
}
void getans(int SG){
if ( SG == 0 ){
if ( vec.size() == 0 ){
puts("Petr");
//find
assert(vec2.size());
printf("%d %d\n",fa[vec2[0]],vec2[0]);
}
else{
puts("Vasya");
}
return;
}
if ( vec.size() == 1 ){
puts("Petr");
int x = vec[0];
if ( SG == f[x] + 1 ){
dfs_ans(x,0);
}
else{
printf("%d %d\n",fa[x],x);
}
}
else if ( vec.size() > 1 ){
puts("Petr");
for (auto x : vec2){
int cur = (SG ^ (f[x] + 1)) - 1;
if ( cur < f[x] ){
dfs_ans(x,cur);
return;
}
}
}
else puts("Vasya");
}
int main(){
//freopen("i.in","r",stdin);
scanf("%d %d",&n,&m);
for (int i = 1 ; i <= m ; i++){
int x,y;
scanf("%d %d",&x,&y);
adde(x,y);
deg[y]++;
}
int SG = 0;
for (int i = 1 ; i <= n ;i++) if ( !deg[i] ){
for (int x = head[i] ; x ; x = e[x].next){
fa[e[x].to] = i;
dfs(e[x].to);
SG ^= f[e[x].to] + 1;
if ( f[e[x].to] ) vec.push_back(e[x].to);
vec2.push_back(e[x].to);
//cout<<i<<" "<<f[i]<<endl;
}
}
getans(SG);
}
codeforces 919F. A Game With Numbers
题解
状态数很少,可以直接求bfs
按照0的个数分层,从确定的状态bfs,相当于拓扑排序
一个状态若能转移到P状态则是N状态
若能转移到D状态则是D,或者不能被转移到也是D
只能转移到N则是P
总结:
正确算状态数!
博弈的转态是图,如果有环就是平局,仍然可以类拓扑排序
还没有写,应该在想出后就快速的实现,不应该浮躁的看更多的题
codeforces 1033G. Chip Game
题解
如果a+b确定,每堆石子数显然可以模(a + b)
简单证明
任意一堆>(a + b), 先手拿完后,后手可以使得重复使得%(a + b)不变
经过两轮后的不变性质一般在博弈中可以化简
然后分类讨论
The winner of G′ can always make a move in the game G and hence also wins G.
Let’s break down the single pile games P based on the number of chips modulo a+b:
0≤v′i<a: As neither Alice nor Bob can make a move, this is a zero game (P=0).
a≤v′i<b: Alice can take at least one move in this pile, and Bob none. This is a strictly positive game (P>0).
b≤v′i<2a: If either of the players makes a move, it changes to a zero game. This is thus a fuzzy game (P||0). Note that this interval may be empty.
max(2a,b)≤v′i<a+b: If Alice makes a move, she can turn this game into a positive game for her. Bob can turn this into a zero game. This is a fuzzy game (P||0).
总结:
这道题本来不难,但是写了一个多小时,还没有调出来
一开始分类讨论情况掉了最后一种,
并且没有想清楚代码该怎么写,就开始写,这样一定会浪费时间。
调试的时候也应该即时停下来,想清楚再修改,超出规定的时间就不能再调了,不能盲目的浪费时间!
codeforces 1037G. A Game on Strings
题解
枚举一开始选哪个字符,分成的多段字符串
直接看成multi-SG,对于每个子游戏分别计算SG值
注意复杂度计算:
对于每个字符,最多分26层,每层区间总长<=n(区间个数更少),所以这一步是26 * 26 * n的
对于询问直接枚举第一步决策,然后发现包括区间两端的不完整区间其实都是算过的。
总结:
要仔细计算复杂度,自己一定要想清楚,不能还没有想清楚就看题解,这样没有意义!
codeforces 1091H. New Year and the Tricolore Recreation
题解
每个状态看成一个二元组(x,y),每个人可以变成x - k或者y - k
进而发现x和y也是独立的
于是得到2 * n个独立子游戏
计算SG的时候用bitset优化。观察或者打表(我直接看了题解)发现SG最大值<=100,那么维护100个bitset
第i个bitset记录拥有SG为i的后继的状态j有哪些。
code
总结:
要利用游戏独立的性质,拆分成最简的子游戏
猜测SG值不大,然后用bitset优化求SG过程