知識點補檔1

博弈論

一. 抽象表示

將組合遊戲中的每一個狀態抽象成圖中的一個點,將每一步決策抽象爲圖中的一條邊。這樣,對於組合遊戲的每一次博弈,我們都可以將其抽象成遊戲圖中的一條從某一頂點到出度爲0的路徑

二. P, N狀態

一個先手勝狀態被認爲是一個N-狀態(因爲下一個玩家即將獲勝),一個後手勝狀態被認爲是一個P-狀態(因爲前一個玩家即將獲勝)。
P-和N-狀態歸納性地描述如下:

一個點v是P狀態當且僅當它的所有後繼爲N狀態
一個點v是N狀態當且僅當它的一些後繼爲P狀態

三. SG函數

給定一個有限子集S\subsetN,令mexS(最小排斥值)爲沒有出現在S中的最小自然數
現在給定一個遊戲圖G = (V, E),則有 g(v)=mex{g(w)(v,w)E}g(v) = mex\lbrace{g(w) | (v, w) {\in} E}\rbrace
性質:

對於任意的局面,若它的SG值爲0,則它任何一個後繼局面的SG值不爲0
對於任意的局面,若它的SG值不爲0,那麼它一定有一個後繼局面的SG值爲0

在我們每次只能進行一步操作的情況下,對於任何的遊戲的和,我們若將其中的任一單一 SG-組合遊戲換成數目爲它的SG 值的一堆石子,該單一 SG-組合遊戲的規則變成取石子游戲的規則(可以任意取,甚至取完),則遊戲的和的勝負情況不變。

四. Anti-SG遊戲

定義:決策集合爲空的遊戲者贏,即取走最後一個石子的人敗
SJ定理:對於任意一個 Anti-SG 遊戲,如果我們規定當局面中所有的單一遊戲的 SG 值爲 0 時,遊戲結束,則先手必勝當且僅當:

  • 遊戲的 SG 函數不爲 0 且遊戲中某個單一遊戲的 SG 函數大於 1
  • 遊戲的 SG 函數爲 0 且遊戲中沒有單一遊戲的 SG 函數大於 1。

五. Multi-SG遊戲

定義:在符合拓撲原則的前提下,一個單一遊戲的後繼可以爲多個單一遊戲,即可以將一堆石子分爲多堆石子
先從Multi-Nim開始:有n堆石子,兩個人可以從任意一堆石子中拿任意多個石子(不能不拿)或把一堆數量不少於2石子分爲兩堆不爲空的石子,沒法拿的人失敗。問誰會勝利。這有一神奇定義:

SG(x)={x1,x  mod  4=0x,x  mod  4=1or2x+1,x  mod  4=3SG(x) = \begin{cases} x-1, & \text{$x\;mod\;4 = 0$} \\ x, & \text{$x\;mod\;4 = 1\,or\,2$} \\ x + 1, & \text{$x\;mod\;4 = 3$} \end{cases}

六. Every-SG遊戲

定義:對於還沒有結束的單一遊戲,遊戲者必須對該遊戲進行一步決策,即每一個可以移動的棋子都要移動
有如下解法:在通過拓撲關係計算某一個狀態點的 SG 函數時(事實上,我們只需要計算該狀態點的 SG 值是否爲 0)對於 SG 值爲 0 的點,我們需要知道最快幾步能將遊戲帶入終止狀態,對於 SG 值不爲 0 的點,我們需要知道最慢幾步遊戲會被帶入終止狀態,我們用step函數來表示這個值。

step(v)={0,vmax(step(u))+1,SG(v)>0uvSG(u)=0min(step(u))+1,SG(v)=0uvstep(v) = \begin{cases} 0, & \text{$v爲終止狀態$} \\ max(step(u))+1, & \text{$SG(v) > 0 \bigcap u爲v的後繼狀態 \bigcap SG(u) = 0$} \\ min(step(u))+1, & \text{$SG(v) = 0 \bigcap u爲v的後繼狀態$} \end{cases}

定理:對於Every-SG遊戲先手必勝當且僅當單一遊戲中最大的step爲奇數

七. 翻硬幣遊戲

一般規則如下:

  • N 枚硬幣排成一排,有的正面朝上,有的反面朝上。我們從左開始對硬幣按 1 到 N 編號。
  • 遊戲者根據某些約束翻硬幣(如:每次只能翻一或兩枚,或者每次只能翻連續的幾枚),但他所翻動的硬幣中,最右邊的必須是從正面翻到反面。
  • 誰不能翻誰輸。

結論:局面的SG值爲局面中每個正面朝上的棋子單一存在時的SG值的異或和

八. 樹的刪邊遊戲

規則如下:

  • 給出一個有 N 個點的樹,有一個點作爲樹的根節點。
  • 遊戲者輪流從樹中刪去邊,刪去一條邊後,不與根節點相連的部分將被移走。
  • 誰無路可走誰輸。

定理:葉子節點的SG值爲0;中間節點的SG值爲它的所有子節點的SG值加1後的異或和

九. 無向圖刪邊遊戲

1. Christmas Game

題目大意:

  • 有 N 個局部聯通的圖。
  • Harry 和 Sally 輪流從圖中刪邊,刪去一條邊後,不與根節點相連的部分將被移走。Sally 爲先手。
  • 圖是通過從基礎樹中加一些邊得到的。
  • 所有形成的環保證不共用邊,且只與基礎樹有一個公共點。
  • 誰無路可走誰輸

有如下性質:

  • 對於長度爲奇數的環,去掉其中任意一個邊之後,剩下的兩個鏈長度同奇偶,異或之後的 SG 值不可能爲奇數,所以它的 SG 值爲 1;
  • 對於長度爲偶數的環,去掉其中任意一個邊之後,剩下的兩個鏈長度異奇偶,異或之後的 SG 值不可能爲 0,所以它的 SG 值爲 0;

所以我們可以去掉所有的偶環,將所有的奇環變爲長短爲1的鏈,這樣就改造成了上一節的模型,算出每一棵樹的SG值之後,再按Nim遊戲異或即可

2. 無向圖刪邊遊戲

對Christmas Game進行一步拓展——去掉對環的限制,規則如下:

  • 一個無相聯通圖,有一個點作爲圖的根。
  • 遊戲者輪流從圖中刪去邊,刪去一條邊後,不與根節點相連的部分將被移走。
  • 誰無路可走誰輸。

對於這一模型,有一著名定理Fusion Principle :

我們可以對無向圖做如下改動:將圖中的任意一個偶環縮成一個新點,任意一個奇環縮成一個新點加一個新 邊;所有連到原先環上的邊全部改爲與新點相連。這樣的改動不會影響圖的 SG 值。

這樣,我們可以將任意一個無向圖改成樹結構,“無向圖刪邊遊戲” 就變成了 “樹的刪邊遊戲”

十.簡單博弈

1.斐波那契博弈

1堆石子有n個,兩人輪流取.先取者第1次可以取任意多個,但不能全部取完.以後每次取的石子數不能超過上次取子數的2倍。取完者勝.先取者負輸出"Second win".先取者勝輸出"First win".

Zeckendorf定理(齊肯多夫定理):任何正整數可以表示爲若干個不連續的Fibonacci數之和

#include<cstdio>
#include<iostream>
using namespace std;
 
int main()
{
    int n, fib[50];
    fib[0] = 2; fib[1] = 3;
    for(int i = 2; i < 50; i++)
        fib[i] = fib[i-1] + fib[i-2];
    while(~scanf("%d", &n) && n){
        int flag = 1;
        for(int i = 0; i < 50; i++){
            if(fib[i] == n){
                flag = 0;
                cout << "Second win" << endl;
            }
            if(fib[i] > n)
                break;
        }
        if(flag){
            cout << "First win" << endl;
        }
    }
    return 0;
}

2.威佐夫博弈

有兩堆各若干個物品,兩個人輪流從某一堆或同時從兩堆中取同樣多的物品,規定每次至少取一個,多者不限,最後取光者得勝

#include <bits/stdc++.h>
using namespace std;
 
int main()
{
	int a, b, c;
	while(~scanf("%d %d", &a, &b)){
		if(a > b)
			swap(a, b);
		c = floor(b-a) *((sqrt(5.0)+1)/2);
		if(a == c)	cout << "0" << endl;
		else	cout << "1" << endl;	
	}
	return 0;
}

3.Nimk博弈

有n堆各若干個物品,兩個人輪流從K堆取任意多的物品,規定每次至少取一個,多者不限,最後取光者得勝

const int MAXN = 10005;
int SG[MAXN];//需要處理的SG值數組
int XOR[MAXN];//儲存每一個二進制位上的和
int xxx;//儲存每一個SG值的臨時變量
int num;//儲存當前SG值有多少位的臨時變量
int maxn;//儲存最大的SG值位數
 
bool solve(int N,int M)//N表示SG數組的大小,從1到N,M表示每次可以取1到M堆
{
	memset(XOR, 0, sizeof XOR);
	maxn = -1;
	for (int i = 1; i <= N; i++)
	{
    	xxx = SG[i];
		num = 0;
		while (xxx) {
			XOR[num] += xxx&1;
			num++;
			xxx >>= 1;
		}
		maxn = max(maxn, num);
	}
	for (int i = 0; i < maxn; i++)
		if (XOR[i] % (M + 1))
			return true;//返回true表示先手必勝
	return false;//返回false表示先手必敗
}

十一.例題

1.HDU–1536

在最簡單的取石子情況中,有如下規律:
{sg(x)=xsg(x)=x  mod  (1+m)可以取1 m個石子sg \begin{cases} sg(x) = x & \text{$可以取任意個石子$} \\ sg(x) = x\;mod\;(1+m) & \text{可以取1~m個石子}\\ sg打表 & \text{$可以取石子個數爲不連續整數$} \end{cases}

#include <bits/stdc++.h>
using namespace std;
 
const int N=10008;//N爲所有堆最多石子的數量
int f[108],sg[N];//f[]用來保存只能拿多少個,sg[]來保存SG值
bool hash[N];//mex{}
void sg_solve(int t,int N)
{
    int i,j;
    memset(sg,0,sizeof(sg));
    for(i=1;i<=N;i++) {
        memset(hash,0,sizeof(hash));
        for(j=1;j<=t;j++)
            if(i - f[j] >= 0)
               hash[sg[i-f[j]]] = 1;
        for(j=0;j<=N;j++)
            if(!hash[j])
                break;
        sg[i] = j;
    }
}
 
 
int main()
{
    int k, m, l, num;
    while(scanf("%d",&k), k) {
        for(i= 1;i <= k;i++) scanf("%d",&f[i]);
        sg_solve(k, N);
        scanf("%d", &m);
        string ans = "";
        for (int i = 1; i <= m; i++) {
            int sum = 0;
            scanf("%d", &l);
            for (int j = 1; j <= l; j++) {
                scanf("%d", &num);
                sum ^= sg[num];
            }
            if(sum == 0) ans+="L";
            else    ans+="W";
        }
        cout<<ans<<endl;
    }
    return 0;
}

2.HDU–1524

博弈的抽象表示,幫助理解SG

#include <bits/stdc++.h>
using namespace std;

const int maxn = 1005;
int sg[maxn], pat[maxn];
vector<int> G[maxn];

int getSG(int u)
{
    if (sg[u] != -1)    return sg[u];
    bool mex[10*maxn];
    memset(mex, false, sizeof(mex));
    for (int i = 0; i < G[u].size(); ++i) {
        int v = G[u][i];
        getSG(v);
        mex[sg[v]] = true;
    }
    for (int i = 0; ;++i) {
        if (!mex[i]) {
            sg[u] = i;
            break;
        }
    }
    return sg[u];
}

int main()
{
    int N, m, p, x;
    while (~scanf("%d", &N)) {
        for (int i = 0; i < N; ++i) {
            G[i].clear();
            pat[i] = 0;
        }
        for (int i = 0; i < N; ++i) {
            scanf("%d", &p);
            while (p--) {
                scanf("%d", &x);
                G[i].push_back(x);
                pat[x]++;
            }
        }
        memset(sg, -1, sizeof(sg));
        for (int i = 0; i < N; ++i) {
            if (pat[i] == 0)    getSG(i);
        }
        while (~scanf("%d", &m), m) {
            int ans = 0;
            for (int i = 0; i < m; ++i) {
                scanf("%d", &x);
                getSG(x);
                ans ^= sg[x];
            }
            if (ans != 0)   cout << "WIN" << '\n';
            else    cout << "LOSE" << '\n';
        }
    }
    return 0;
}
3.BZOJ–1299

對於一個博弈遊戲而言,重要的是通過操作留給對面一個sg=0sg=0的局面

#include<cstring>
#include<cstdio>
using namespace std;
int n,sg[2001],a[2001];
bool found;
int dfs(int x,int used,int now)
{
     if(x==n+1)
     {
     	   if(!now&&used>0)found=1;
     	   return 0;
     }
     dfs(x+1,used,now);
     dfs(x+1,used+1,now^a[x]);
}
int main()
{
	   for(int t=1;t<=10;t++)
	   {
	   	   memset(sg,-1,sizeof(sg));
					found=0;
	       scanf("%d",&n);
	       for(int i=1;i<=n;i++)
	       	   scanf("%d",&a[i]);
    	   dfs(1,0,0);
    	   if(found)puts("NO");
    	   else puts("YES");
	   }
	   return 0;
}

類Green博弈

現有一棵樹,樹的每條邊都有長度,A和B輪流染色,每次可選擇一條邊進行染色,染色長度\leq邊長,已染色的邊不能再染色,同時將子節點刪除。

green博弈變形,對於都是1的就是green博弈SG[u]SG[u] ^=SG[v]+1=SG[v]+1

對於大於1的邊,偶數對其沒有貢獻,奇數有貢獻,SG[u]SG[u] ^$= SG[v] KaTeX parse error: Expected group after '^' at position 1: ^̲(val[v] % 2)$

#include <bits/stdc++.h>
using namespace std;

const int maxn = 2005;
int cnt;
struct Node {
    int to, val, next;
}edge[maxn];

int sg[maxn], head[maxn];

void add(int u, int v, int w)
{
    edge[cnt].to = v;
    edge[cnt].val = w;
    edge[cnt].next = head[u];
    head[u] = cnt++;
}

void dfs(int u, int fa)
{
    sg[u] = 0;
    for (int i = head[u]; ~i; i = edge[i].next) {
        int v = edge[i].to;
        if (v == fa) continue;
        dfs(v, u);
        if (edge[i].val == 1) sg[u] ^= (sg[v]+1);
        else    sg[u] ^= (sg[v] ^ (edge[i].val % 2));
    }
}
int main()
{
    int T;
    cin >> T;
    for (int j = 1; j <= T; ++j) {
        memset(head, -1, sizeof(head));
        cnt = 0;
        int n, u, v, w;
        scanf("%d", &n);
        for (int i = 1; i < n; ++i) {
            scanf("%d %d %d", &u, &v, &w);
            add(u, v, w);
            add(v, u, w);
        }
        dfs(0, 0);
        printf("Case %d: ", j);
        if (sg[0])  cout << "Emily" << '\n';
        else    cout << "Jolly" << '\n';

    }
    return 0;
}

階梯博弈

n個人隨機的站在若干級臺階上,最高一級臺階必須站人,A和B分別移動某個人向上走任意走多級臺階,不能越過更高級臺階上的人,且同一臺階上只能站一人,無法繼續移動者輸。給出n個人位置,計算先手必勝策略。

#include <stdio.h>
#include <string.h>
const int N = 100 + 10;
int a[N],b[N];
int main()
{
    int n = 0,i,j,k,sum = 0;
    while(scanf("%d",&a[n])!=EOF)
        n++;
    for(i=1; i<n; ++i)
        b[i-1] = a[i] - a[i-1] - 1;
    for(i=0; i<n-1; i+=2)
        sum ^= b[i];
    if(sum==0)
        printf("-1\n");
    else
    {
        //枚舉第i個人移動j步,使得剩下的局面異或等於0,
        for(i=0; i<n-1; ++i)
            for(j=1; a[i]+j<a[i+1]; ++j)
            {
                
                b[i] -= j;
                if(i!=0)
                    b[i-1] += j;
                
                sum = 0;
                for(k=0; k<n-1; k+=2)
                    sum ^= b[k];
                if(sum==0)
                {
                    printf("%d %d\n",a[i],a[i]+j);
                    break;
                }
                b[i] += j;
                if(i!=0)
                    b[i-1] -= j;
                
            }
    }
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章