SG函數
說到博弈論就不得不說到SG函數,說到SG函數就不得不說今年將AK NOI的mjt。
作用
對於一個狀態i為先手必勝態當且僅當SG(i)!=0。
轉移
那怎么得到SG函數尼。
SG(i)=mex(SG(j))(狀態i可以通過一步轉移到j)
首先說一下mex。一個集合的mex是最小的沒有出現在這個集合里的非負整數。
其實想一下這個也是挺明顯的。狀態i是先手必敗態當前僅當i轉移到的狀態都是先手必勝態。同樣,只要當前狀態可以轉移到一個先手必敗態,那么當前就是先手必勝態。
小定理
對於兩個獨立的游戲A,B,他們的SG函數=SG(A) ^ SG(B)
這個比較難描述,看一下后面的題就明白了。
真模板題
先來娛樂一下
題面
給一個有向無環圖,一開始在某個節點有個棋子,Alice和Bob輪流操作,每次可以把石子移動一步。最后無法移動者失敗。問是否存在先手必勝策略。
思路
SG函數的定義題。
出度為0的點的SG函數為1。每個點可以轉移到的狀態是可以走到的節點。直接在DAG上dp即可。
Nim游戲
博弈論入門必學。
Alice,Bob和一些石子的故事
level 1
有n堆石子,第i堆石子有a[i]個,Alic和Bob輪流選擇一堆從里面拿走任意多個石子(不能不拿),最后無法操作者失敗,問是否存在先手必勝態。
思路
這就是上面那個小定理的應用了。考慮只有一堆石子的情況,肯定是先手必勝態。SG函數肯定是石子個數(顯然)。
那么整個游戲的SG函數就是每個子游戲(只有一堆石子的情況)SG函數的異或之和。
所以這個題只要判斷所有石子個數異或和是不是0即可。
level 2
有一堆共n個石子,Alice和Bob輪流從這堆石子里面取1到k個,不能取者輸。問是否存在先手必勝策略
思路
狀態i可以轉移到的狀態就是i-j(1<=j<=k)
所以SG(i)=mex(SG(i-j)) (1<=j<=k)
假設k=3
則SG(0)=0,SG(1)=1,SG(2)=2,SG(3)=3,SG(4)=0,SG(5)=1...
可以發現,到4的時候開始重新從0開始。仔細想一下就能發現為啥了。這就相當於拿個長度為k的窗口在這個序列里面滑動。新進來的數的SG函數就是剛好從窗口的出去的那個。
所以只要判斷所以當前游戲的SG函數就是n%(k+1)
level 3
有一堆n個石子,Alice和Bob輪流這堆石子里面取石子,每次可以取l-r個,問是否存在先手必勝態。
思路
找規律!!
假設l=3,r=7
SG=[0,0,0,1,1,1,2,2,2,3,0,0.....]
發現SG(i)=i%(l + r) / l
dyh:sg函數的取值不容易直接思考,並且如果找到了規律 ⼀般都可以⽤數學歸納法證明。
level 4
有n堆石子,第i堆石子有a[i]個,Alice和Bob輪流操作,每次可以從一堆石子里面取任意多個,也可以把一堆石子分成兩堆。不能操作者輸。問是否存在先手必勝態。
思路
SG(i)=mex(SG(i - j),SG(i-j) ^ SG(j))
SG(i-j)是從石子里面取j個,SG(i-j)^SG(j)是將石子分成j和i-j兩堆。
level 5
有1堆石子,Alice和Bob輪流操作,每次可以從一堆里面取當前個數的因數個(不能是本身),不能操作者(剩下一個)輸。問是否存在先手必勝態。
思路
SG(i) = mex(SG(i-k)) (k|i)
找規律可以發現
SG(i) = __builtin_ctz(i)
也就是i的末尾0的個數
level 6
有1堆石子,Alice和Bob輪流操作,每次可以從一堆里面取與當前個數互質的數字個石子,不能操作者輸,問是否存在先手必勝態
思路
SG(i)=mex(SG(i-j)) (gcd(i,j)=1)
打表如下
發現,對於偶數,SG為0.
對於奇數,SG為最小質因子的編號(就是最小質因子是第幾個質數)
雜題
小題題
給定一個n*m的網格,第一行和第一列的元素標記着為勝或者為負,在某個位置有一個石子,Alice和Bob輪流操作,每次可以把這個石子往左或者往上移動一步。問是否存在先手必勝策略。
思路
先建個圖,就成了一個DAG上的dp。找規律可以發現對角線上的SG函數都是相同的。
CF388C
有n排石子,每排有若干堆。Ciel可以選擇一排,拿走這一排的第一堆石子。Jiro可以選擇一排,拿走這一排的最后一堆石子。兩個人都想要讓自己的石子數量最多。問兩個人最后的石子數量。Ciel先手。
思路
先考慮Ciel,他肯定可以保證自己至少得到每排石子的左半邊。只要按照下面的原則做就可以:
第一個石子先隨便選,如果Jiro和自己拿同一排,那就隨便再選一個。
如果Jiro沒和自己拿同一排,那就去和Ciel拿同一排。
考慮Jiro,同樣可以保證自己至少得到每排石子的右半邊。只要每次都和Ciel選同一排即可。
綜上,就可以確定Ciel和Jiro分別拿了左半邊和右半邊。
對於奇數排的中間那一堆,從大到小從Ciel開始輪流選就行了。
/*
* @Author: wxyww
* @Date: 2019-03-06 19:55:16
* @Last Modified time: 2019-03-06 19:58:43
*/
#include<cstdio>
#include<iostream>
#include<cstdlib>
#include<cmath>
#include<ctime>
#include<bitset>
#include<cstring>
#include<algorithm>
#include<string>
#include<queue>
#include<vector>
using namespace std;
typedef long long ll;
const int N = 1000;
ll read() {
ll x=0,f=1;char c=getchar();
while(c<'0'||c>'9') {
if(c=='-') f=-1;
c=getchar();
}
while(c>='0'&&c<='9') {
x=x*10+c-'0';
c=getchar();
}
return x*f;
}
ll ans1,ans2;
int m;
ll a[N];
bool cmp(int x,int y) {
return x > y;
}
int main() {
int n = read();
for(int i = 1;i <= n;++i) {
int k = read();
int z = k >> 1;
for(int j = 1;j <= z;++j) ans1 += read();
if(k & 1) a[++m] = read();
for(int j = 1;j <= z;++j) ans2 += read();
}
sort(a + 1,a + m + 1,cmp);
for(int i = 1;i <= m;++i) {
if(i & 1) ans1 += a[i];
else ans2 += a[i];
}
printf("%I64d %I64d\n",ans1,ans2);
return 0;
}