每日打卡 2017.04.02 博弈论专题

https://vjudge.net/contest/156519#overview



打表:比如求斐波那契的第k项,k<=1000,很多case,这时可以预处理的时候把1-1000全部求出来并储存,这就叫打表


HDU 2176 Nim博弈


有若干堆石子,双方轮流选择其中一堆,并拿走能拿走的任意正整数个石子。最后不能再取石子者输。


结论:NIM博弈问题

把所有堆的石子数量做异或和。结果为0,则先取者输。

当结果不为0时,先取者赢,即,取正好个石子(这个取法总是存在的,否则最高位1是得不到的),使得异或和为0,此时对面无论怎么取,总会使得异或和不再为0,之后再,取正好个使其为0即可。

利用这个结论,就能求出输家和赢家的第一次取法。

Tip:C语言中,异或是^。a^a=0,具有交换律和结合律,因此(a^b^c^d)^a=b^c^d,可求出取法


这个结论中,需要记住异或和为0是必败态,也就是P-position

下面我们证明这个结论:

我们记作P-position为“后者可保证必胜”或者“先手必败”(Previous),N-position为“先手可保证必胜”(Next)。(可保证是指仅在采取特定特略下必胜)

1.无法移动的局面(terminal positon)是P-positon

2.一个局面如果当前存在一种策略使得局面变为P-position,则当前局面为N-position

3.一个局面如果任何策略都只能使得局面变成N-position,则当前局面为P-position

如果局面不可重现(即position的集合能够拓扑排序,石子只会越来越少不能放回),则每个postion非P即N。每个局面可以计算他的子局面(下一个可能的局面)来判断PN(存在一个P子局面则为N,全为N局面则为P)

我们证为什么异或能判断PN,只需证明如下3点:

1.所有的terminal position在异或下均为P-position(即异或和为0)

2.对于任意N-position,存在一种策略使其变为P-position

3.对于任意P-position,任何可能的策略都会变成对手的N-position

对于第一点,terminal position指的是任何无法移动的局面,也即(0,0,...,0),而任意个0做异或还是为0(有限和0和1做异或结果是1的个数是否为奇数)

对于第二点,已知有a1^a2^...^an=k!=0,对于k最高位的1,必然存在某个ai的对应位为1(否则最高位1是得不到的),那我们令ai'=ai^k,则原式=k^k=0

对于第三点,已知有a1^a2^...^an=0,假设ai变为ai'使得原式=0,则a1^...^ai^...=0且a1^...^ai^...=0,根据异或的消去律知ai=ai',不合法

因此通过判断一个局面的异或和是否为0就能判断出position状态。


#include<iostream>
#include<cstdio>
#define sf(a) scanf("%d", &a)
using namespace std;

const int MAXN = 200000 + 5;
int a[MAXN];
int m;

int main()
{
	freopen("input.txt", "r", stdin);
	while(sf(m) != EOF && m != 0)
	{
		sf(a[0]);
		int p = a[0];
		for(int i = 1; i < m; i++)
		{
			sf(a[i]);
			p = p^a[i];
		}
		if(p == 0) printf("No\n");
		else
		{
			printf("Yes\n");
			for(int i = 0; i < m; i++)
			{
				int q = p^a[i];
				if(a[i] >= q)
				{
					printf("%d %d\n", a[i], q);
				}
			}
		}
	}
	return 0;
}

解题关键:

首先暴力打表找规律,猜出必败态

然后证明这个必败态(一般不证):

首先,末状态为必败态。
其次,可以证明任何一个胜态都有策略变成必败态。
最后,证明任何一个必败态都无法变成另一个必败态。

然后求出SG函数即可


HDU 2177 威佐夫博弈

注:下列程序是打表范例

#include<iostream>
#include<cstdio>
using namespace std;

const int MAXN = 50;
int m[MAXN][MAXN];

int main()
{
	freopen("input.txt", "r", stdin);
	for(int i = 0; i < MAXN; i++)
	for(int j = 0; j < MAXN; j++)
	{
		if(m[i][j] == 0)
		{
			for(int p = 1; i + p < MAXN; p++)	m[i+p][j] = 1;
			for(int p = 1; j + p < MAXN; p++)	m[i][j+p] = 1;
			for(int p = 1; j + p < MAXN && i + p < MAXN; p++)
				m[i+p][j+p] = 1;
		}
	}
	for(int i = 0; i < MAXN; i++)
	for(int j = i; j < MAXN; j++)
	if(m[i][j] == 0)printf("%d\t%d\n", i, j);
	return 0;
}
其结果:


0 0
1 2
3 5
4 7
6 10
8 13
9 15
11 18
12 20
14 23
16 26
17 28
19 31
21 34
22 36
24 39
25 41
27 44
29 47
30 49

归纳总结:小者是从未出现过的自然数,大者加k(这种规律没有通项,sg函数也是前面没有出现过的数,必须牢记!)


#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
using namespace std;

const int MAXN = 1000000+5;
int vis[MAXN];
int ans[MAXN];
int anst[MAXN];
int num[MAXN];
int a, b;

int main()
{
	freopen("input.txt", "r", stdin);
	int p, q;
	p = q = 0;
	int k = 0;
	memset(ans, -1, sizeof(ans));
	memset(anst, -1, sizeof(anst));
	while(q<MAXN)
	{
		ans[p] = q;
		anst[q] = p;
		vis[q] = 1;
		num[k] = p;
		while(vis[p] == 1)p++;
		vis[p] = 1;
		k++;
		q = p + k;
	}
	while(scanf("%d%d", &a, &b) && b != 0)
	{
		if(ans[a] == b) printf("0\n");
		else
		{
			printf("1\n");
			k = b-a;
			if(num[k]<a && ans[num[k]] < b) printf("%d %d\n", num[k], ans[num[k]]);
			if(ans[a] < b && ans[a] != -1) printf("%d %d\n", a, ans[a]);
			if(anst[a] < b && anst[a] != -1 && a != b) printf("%d %d\n", anst[a], a);
			if(anst[b] < a && anst[b] != -1) printf("%d %d\n", anst[b], b);
		}
	}
	return 0;
}

打表即可,15ms,14.8MB

HDU 1527 威佐夫博弈升级版


此题是2177升级版。然而,此题题目所给的范围是1e9,打表显然是不可行,最多到5e7。这种情况必须要用模型


威佐夫博弈:

威佐夫博弈(Wythoff Game):有两堆各若干个物品,两个人轮流从某一堆或同时从两堆中取同样多的物品,规定每次至少取一个,多者不限,最后取光者得胜。

这种情况下是颇为复杂的。我们用(ak,bk)(ak ≤ bk ,k=0,1,2,...,n)表示两堆物品的数量并称其为局势,如果甲面对(0,0),那么甲已经输了,这种局势我们称为奇异局势。前几个奇异局势是:(0,0)、(1,2)、(3,5)、(4,7)、(6,10)、(8,13)、(9,15)、(11,18)、(12,20)。
可以看出,a0=b0=0,ak是未在前面出现过的最小自然数,而 bk= ak + k。

通项公式:ak =[k(1+√5)/2],bk= ak + k (k=0,1,2,...n 方括号表示取整函数)

具体细节见百度百科

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std;

int a, b;

int main()
{
	freopen("input.txt", "r", stdin);
	double s = (sqrt(5)+1)/2;
	while(scanf("%d%d", &a, &b) != EOF)
	{
		int p = min(a, b);
		int q = max(a, b);
		int k = q-p;
		if((int)(k*s) == p) printf("0\n");
		else printf("1\n");
	}
	return 0;
}


HDU 2516 斐波那契博弈


二维SG函数记忆化dp打表:

记作sg[i][j]为j个石子最多取i个。当我们不讨论具体解是什么的时候,sg函数的值为0、1即可。

#include<iostream>
#include<cstdio>
#include<cstring>
#define clr(a,b) memset(a, b, sizeof(a))
using namespace std;

const int MAXN = 10000;
int sg[MAXN][MAXN];

int dfs(int a, int b)
{
	if(sg[a][b] != -1)return sg[a][b];
	if(a >= b) return sg[a][b] = 1;
	for(int i = 1; i <= a; i++)
		if(dfs(2*i, b-i) == 0)return sg[a][b] = 1;
	return sg[a][b] = 0;
}

int main()
{
	freopen("input.txt", "r", stdin);
	clr(sg,-1);
	sg[1][2] = 0;
	sg[0][0] = 0;
	for(int i = 2; i < MAXN; i++)
		if(dfs(i-1, i) == 0) printf("%d\n", i);
	return 0;
}
其结果:

2
3
5
8
13
21
34
55
89
144
233
377
610
987
1597
2584
4181

可以看出是一个斐波那契数列时,先手必败。

#include<iostream>
#include<cstdio>
#include<set>
using namespace std;

set<int> S;

int main()
{
	//freopen("input.txt", "r", stdin);
	int n;
	int a = 1, b = 2;
	S.insert(1);
	while(b > 0)
	{
		S.insert(b);
		b = a + b;
		a = b - a;
	}
	while(scanf("%d", &n) && n != 0)
	{
		if(S.count(n) == 0) printf("First win\n");
		else printf("Second win\n");
	}
	return 0;
}


HDU 2188 巴什博奕

#include<iostream>
#include<cstdio>
using namespace std;

int n, m, kase;

int main()
{
	freopen("input.txt", "r", stdin);
	scanf("%d", &kase);
	while(kase--)
	{
		scanf("%d%d", &n, &m);
		if(n%(m+1) != 0) printf("Grass\n");
		else printf("Rabbit\n");
	}
	return 0;
}



HDU 2897 巴什博奕升级版

这种没有确定维数的无法打表,只能自己手推
[1,p] P-position
[p+1,p+2,...,p+q] N-positon
[p+q+1,...,p+q+p] P-position
[p+q+p+1,...,p+q+p+q] N-position
n%(p+q),当结果为[1,p]为P-positon,否则为N-positon
#include<iostream>
#include<cstdio>
using namespace std;

int n, p, q;

int main()
{
	freopen("input.txt", "r", stdin);
	while(scanf("%d%d%d", &n, &p, &q) != EOF)
	{
		int m = n % (p + q);
		if(m >= 1 && m <= p) printf("LOST\n");
		else printf("WIN\n"); 
	}
	return 0;
}
巴什博奕:n%(p+q) == 0,结论需要记住!


HDU 1079

sg打表,记忆化dp居然直接过了- -|| 15ms

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;

int sg[2002][13][32];
int day[] = {0,31,28,31,30,31,30,31,31,30,31,30,31};

int leap(int year)
{
	if(year % 4 != 0) return 0;
	return year != 1900;
}

int dfs(int y, int m, int d)
{
	if(sg[y][m][d] != -1) return sg[y][m][d];
	if(m < 12 && day[m] <= day[m+1]) if(dfs(y,m+1,d) == 0) return sg[y][m][d] = 1;
	if(m == 12) if(dfs(y+1,1,d) == 0) return sg[y][m][d] = 1;
	if(m == 2 && leap(y) && d == 29) 
	{
		if(dfs(y,3,1) == 0) return sg[y][m][d] = 1;
	}
	else
	{
		if(d == day[m])
		{
			if(m == 12)
			{
				if(dfs(y+1,1,1) == 0) return sg[y][m][d] = 1;
			}
			else
			{
				if(dfs(y,m+1,1) == 0) return sg[y][m][d] = 1;
			}
		}
		else
		{
			if(dfs(y,m,d+1) == 0) return sg[y][m][d] = 1;
		}
	}
	return sg[y][m][d] = 0;
}

int main()
{
	freopen("input.txt", "r", stdin);
	int kase;
	int y, m, d;
	scanf("%d", &kase);
	memset(sg, -1, sizeof(sg));
	sg[2001][11][4] = 0;
	sg[2001][12][1] = 1;
	sg[2001][12][2] = 1;
	sg[2001][12][3] = 1;
	sg[2001][12][4] = 1;
	for(int i = 5; i < 31; i++)
		sg[2001][11][i] = 1;
	while(kase--)
	{
		scanf("%d%d%d", &y, &m, &d);
		if(dfs(y,m,d) == 1) printf("YES\n");
		else printf("NO\n");
	}
	return 0;
}


下面是另外一种解法:

万一爆炸,穷举,可以发现,基本上每隔1个就是一个P点,但是并不是奇偶,而是月份+天数等于奇数为P点,与年份无关
而且必须要看仔细!9月30日和11月30是N点不是P点!找规律必须耐心仔细(kan yun qi)
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;

int main()
{
	freopen("input.txt", "r", stdin);
	int kase;
	int y, m, d;
	scanf("%d", &kase);
	while(kase--)
	{
		scanf("%d%d%d", &y, &m, &d);
		if((m + d) % 2 == 0 || (d == 30 && (m == 9 || m == 11))) printf("YES\n");
		else printf("NO\n");
	}
	return 0;
}



HDU 1525

打表、找规律全部失败,只能理论分析
设a >= b
若a%b == 0, 则直接是必胜态
否则:
如果a > 2b,最后一定会变成(k,b)k=a-nb,如果(k,b)是必败态,直接变化为(k,b)即可取胜;如果(k,b)是必胜态,则直接变化为(k+b,b),此时唯一的后路是(k,b),即(k+b,b)是必败态。因此,当a>=2b,必然是必胜态
当b<a<2b时,唯一的后路是(b,a-b)再做上述判断即可
#include <iostream>
#include<cstdio>
#include<algorithm>
using namespace std;

int a, b, m, n;

int main()
{
    freopen("input.txt", "r", stdin);
    while(scanf("%d%d", &a, &b) != EOF)
    {
        if(a == 0 && b == 0) break;
        int ans = 0;
        m = max(a, b);
        n = min(a, b);
        while(true)
        {
            if(m % n == 0 || m > 2*n)break;
            m -= n;
            swap(m, n);
            ans++;
        }
        if(ans % 2 == 0) printf("Stan wins\n");
        else printf("Ollie wins\n");
    }
    return 0;
}


HDU 2149 典型巴什博奕
#include <iostream>
#include<cstdio>
#include<algorithm>
using namespace std;

int m, n;

int main()
{
    freopen("input.txt", "r", stdin);
    while(scanf("%d%d", &m, &n) != EOF)
    {
        if(m % (n + 1) == 0) printf("none\n");
        else
        {
            if(m <= n)
            {
                for(int i = m; i <= n; i++)
                    if(i == m)printf("%d", i);
                    else printf(" %d", i);
                printf("\n");
            }
            else
            {
                printf("%d\n", m % (n + 1));
            }
        }
    }
    return 0;
}


HDU 1850 典型NIM博弈
#include <iostream>
#include<cstdio>
#include<algorithm>
using namespace std;

int m;
int a[101];

int main()
{
    freopen("input.txt", "r", stdin);
    while(scanf("%d", &m) != EOF && m != 0)
    {
        for(int i = 0; i < m; i++) scanf("%d", &a[i]);
        int p = a[0];
        for(int i = 1; i < m; i++) p ^= a[i];
        if(p == 0)printf("0\n");
        else
        {
            int ans = 0;
            for(int i = 0; i < m; i++)
            {
                p ^= a[i];
                if(a[i] >= p) ans++;
                p ^= a[i];
            }
            printf("%d\n", ans);
        }
    }
    return 0;
}


HDU 1846 典型巴什博奕
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

int main()
{
    freopen("input.txt", "r", stdin);
    int kase, n, m;
    cin >> kase;
    while(kase--)
    {
        cin >> n >> m;
        if(n % (m + 1) == 0)printf("second\n");
        else printf("first\n");
    }
    return 0;
}



HDU 1847 典型SG打表
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

const int MAXN = 1001;
int sg[MAXN];
void init()
{
    for(int i = 1; i < MAXN; i*=2)
    {
        sg[i] = 1;
    }
    for(int i = 1; i < MAXN; i++)
    {
        if(sg[i] == 1)continue;
        int q = 1;
        while(i - q > 0)
        {
            if(sg[i-q] == 0)
            {
                sg[i] = 1;
                break;
            }
            q*=2;
        }
    }
}

int main()
{
    freopen("input.txt", "r", stdin);
    init();
    int n;
    while(scanf("%d", &n) != EOF)
    {
        if(sg[n] == 1) printf("Kiki\n");
        else  printf("Cici\n");
    }
    return 0;
}

HDU 3032  SG函数打表找规律
如果只有1堆,N
如果有2堆相同的,P。因为如果A取完某一堆,B取完另一堆;或者A不取完全部,B把剩下那堆取到相同数又留给A;如果A将其中一堆分为两堆,不妨设a<=b<c,则B再将c分为a、b,这样又是相同的两堆和相同的两堆,采上面的策略后生即必胜。
如果两堆不同的,取到剩下2堆相同即可,N
当堆数3堆,情况就复杂了,但如果有两堆相同,那么情况同只有1堆,必胜。这时候我们讨论3堆全部不同的情况,记为a<b<c,比如1,5,6,这是必胜态,也就是当c=a+b时,将c分为(a,b),剩下4堆全部相同,也就是0堆的情况P,因此N;否则………………没法分析,陷入瓶颈

从头开始分析,对于单独的有x的个石子的一堆,其与其他堆相互独立,是个单独的子游戏。
其实再回过头看Nim的取m堆石子游戏。对于任意一堆,sg(x)=x,因此只剩下任意一堆的必然是sg!=0,而m堆做了异或和,也就必然是总游戏的sg函数
回到这题,x个石子可以变成0,1,2,...,x-1,(1,x-1),(2,x-2),...,(k,x-k),这唯一的x+k种情况,对于多堆的情况,求其异或和就是了。我们尝试sg函数打表找规律呢?
(一切游戏的所有子游戏的sg函数的异或和,是这个总游戏的sg函数)
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

const int MAXN = 1000 + 5;
int sg[MAXN];
int hash[MAXN];

int main()
{
    freopen("input.txt", "r", stdin);
    sg[0] = 0;
    sg[1] = 1;
    int n = 30;
    for(int i = 2; i < n; i++)
    {
        memset(hash, 0, sizeof(hash));
        for(int j = 1; j <= i; j++)
        {
            hash[sg[i - j]] = 1;
        }
        for(int j = 1; j <= i / 2; j++)
        {
            hash[sg[i - j]^sg[j]] = 1;
        }
        for(int j = 0; j <= n; j++)
        {
            if(hash[j] == 0)
            {
                sg[i] = j;
                break;
            }
        }
    }
    for(int i = 0; i < n; i++) cout << i << "\t" << sg[i] << endl;
    return 0;
}
打表结果:
0 0
1 1
2 2
3 4
4 3
5 5
6 6
7 8
8 7
9 9
10 10
11 12
12 11
13 13
14 14
15 16
16 15
17 17
18 18
19 20
20 19
21 21
22 22
23 24
24 23
25 25
26 26
27 28
28 27
29 29

可以发现:sg[0]=0,当x大于0时,sg[4k+1] = 4k+1, sg[4k+2] = 4k+2, sg[4k+3] = 4k + 4, sg[4k+4] = 4k + 3

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

int sg(int p)
{
    if(p % 4 == 0) return p - 1;
    if(p % 4 == 3) return p + 1;
    return p;
}

int main()
{
    freopen("input.txt", "r", stdin);
    //freopen("output.txt", "w", stdout);
    int Kase, n, tmp;
    scanf("%d", &Kase);
    while(Kase--)
    {
        scanf("%d", &n);
        scanf("%d", &tmp);
        int ans = sg(tmp);
        for(int i = 1; i < n; i++)
        {
            scanf("%d", &tmp);
            tmp = sg(tmp);
            ans ^= tmp;
        }
        if(ans == 0) printf("Bob\n");
        else printf("Alice\n");
    }
    return 0;
}

fzu 2240 sg+线段树
首先,每一堆石子的sg函数可以求出来,然后再区间上做异或和,再单点更新。100000的复杂度自然而然想到线段树。
打表:
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;

const int MAXN = 105;
int sg[MAXN];
int hash[MAXN];

int main()
{
    freopen("input.txt", "r", stdin);
    freopen("output.txt", "w", stdout);
    sg[1] = 0;
    for(int i = 1; i < 35; i++)
    {
        memset(hash, 0, sizeof(hash));
        for(int j = 1; j <= i/2; j++)
        {
            hash[sg[i-j]] = 1;
        }
        for(int j = 0; j <= i; j++)
        {
            if(hash[j] == 0)
            {
                sg[i] = j;
                break;
            }
        }
        cout << i << '\t' << sg[i] << endl;
    }
    return 0;
}

其结果:
1 0
2 1
3 0
4 2
5 1
6 3
7 0
8 4
9 2
10 5
11 1
12 6
13 3
14 7
15 0
16 8
17 4
18 9
19 2
20 10
21 5
22 11
23 1
24 12
25 6
26 13
27 3
28 14
29 7
30 15
31 0
32 16
33 8
34 17
可以发现,当x为偶数,sg为x/2
以及
sg[4k+1] = k;
sg[8k+3] = k;
sg[16k+7] = k;
sg[32k+15] = k;
sg[64k+31] = k;
………………
即,对于x,若x为偶数,则sg[x]=x/2
若x为奇数,做对(x+1)做除2直到变为奇数m,然后sg[x]=(m-1)/2;(大概是30次运算)

再配上线段树单点更新区间异或和,就是一道典型的综合题了
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;

const int MAXN = 100000+5;

int tmp;
struct Node
{
    int left;
    int right;
    int mid;
    void ass(int l, int r)
    {
        left = l;
        right = r;
        mid = (l + r) >> 1;
    }
    int sum;
}node[MAXN << 2];
int pos[MAXN];

int sg(int v)
{
    if(v & 1)
    {
        v += 1;
        do
        {
            v >>= 1;
        }while((v & 1) == 0);
        return (v - 1) / 2;
    }
    else return v >> 1;
}

void buildtree(int nodenum, int left, int right)
{
    node[nodenum].ass(left, right);
    if(left == right)
    {
        scanf("%d", &tmp);
        node[nodenum].sum = sg(tmp);
        pos[left] = nodenum;
    }
    else
    {
        buildtree(nodenum << 1, left, node[nodenum].mid);
        buildtree(nodenum << 1 | 1, node[nodenum].mid + 1, right);
        node[nodenum].sum = node[nodenum << 1].sum ^ node[nodenum << 1 | 1].sum;
    }
}

void pushup(int nodenum)
{
    node[nodenum].sum = node[nodenum << 1].sum ^ node[nodenum << 1 | 1].sum;
    if(nodenum != 1)pushup(nodenum >> 1);
}

int querytree(int nodenum, int left, int right)
{
    if(node[nodenum].left == left && node[nodenum].right == right) return node[nodenum].sum;
    else
    {
        if(right <= node[nodenum].mid) return querytree(nodenum << 1, left, right);
        else
        {
            if(left > node[nodenum].mid) return querytree(nodenum << 1 | 1, left, right);
            else return querytree(nodenum << 1, left, node[nodenum].mid) ^ querytree(nodenum << 1 | 1, node[nodenum].mid + 1, right);
        }
    }
}

int main()
{
    int a, b, x, k, m, n;
    freopen("input.txt", "r", stdin);
    //freopen("output.txt", "w", stdout);
    while(scanf("%d%d", &n, &m) != EOF)
    {
        buildtree(1, 1, n);
        while(m--)
        {
            scanf("%d%d%d%d", &a, &x, &b, &k);
            node[pos[a]].sum = sg(x);
            pushup(pos[a] >> 1);
            if(querytree(1, b, k) == 0) printf("suneast\n");
            else printf("daxia\n");
        }
    }
    return 0;
}


POJ   2960
加强版的博弈,数据规模比较大
这里我采用了取最大值的办法,因复杂度是T倍的一百万,事实上没必要因为题目要求是2000ms,而直接开最大是1147ms,不然就是969ms
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;

const int MAXN = 10001;

int sg[MAXN], hash[MAXN];
int s[105], h[105][105], maxv;
int k, m, l[105];

void init()
{
    sg[0] = 0;
    for(int i = 1; i <= maxv; i++)
    {
        memset(hash, 0, sizeof(hash));
        for(int j = 0; j < k; j++)
        {
            if(i - s[j] >= 0)
            {
                hash[sg[i-s[j]]] = 1;
            }
        }
        for(int j = 0; j <= maxv; j++)
        {
            if(hash[j] == 0)
            {
                sg[i] = j;
                break;
            }
        }
    }
}

int main()
{
    freopen("input.txt", "r", stdin);
    while(scanf("%d", &k) && k != 0)
    {
        for(int i = 0; i < k; i++) scanf("%d", &s[i]);
        scanf("%d", &m);
        maxv = 0;
        for(int i = 0; i < m; i++)
        {
            scanf("%d", &l[i]);
            for(int j = 0; j < l[i]; j++)
            {
                scanf("%d", &h[i][j]);
                if(h[i][j] > maxv) maxv = h[i][j];
            }
        }
        init();
        for(int i = 0; i < m; i++)
        {
            int ans = sg[h[i][0]];
            for(int j = 1; j < l[i]; j++) ans ^= sg[h[i][j]];
            if(ans == 0) printf("L");
            else printf("W");
        }
        printf("\n");
    }
    return 0;
}

hdu 3537  打完表找不出的规律 子游戏划分

对于x=0, 必败
对于x=1, 无论a[0]=?,都是先手必胜
对于x=2, 无论a[0],a[1]=?,都是先手必胜
对于x=3, 无论a[0],a[1],a[2]=?,都是先手必胜
(0,1,2,3)P,(0,1,2,3,4)N,(0,1,2,3,4,5)N,(0,1,2,3,4,5,6)N,
(0,1,2,3,4,5,6,7)?
对于(0,1,2,4),必胜,因为走(3,4)就给了对手P
对于(0,1,3,4)(0,2,3,4)(1,2,3,4)同理必胜,走(k,4)必胜
(0,1,2,5)N,(0,1,3,5)N,(0,2,3,5)N
(0,1,4,5)P:(0,1,2,5)N,(0,1,3,5)N,(0,1,2,4)N,(0,1,3,4)N,(0,1,2,3,4)N,(0,1,2,3,5)N
(0,2,4,5)N,(0,3,4,5)N………………无法分析,换思路


对于一个Nim游戏,我们不能盲目的给他增加状态的维数,而是应该想办法将其化成不相互独立的子游戏,然后做异或和。
对于这题,对于n个硬币,然后输入n个位置,那我应该想到,也许单个位置就是对应一个sg函数?

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;

const int MAXN = 500 + 5;
int sg[MAXN], hash[MAXN];

int main()
{
    freopen("output.txt", "w", stdout);
    int n = 34;
    sg[0] = 1;
    sg[1] = 2;
    for(int i = 2; i < n; i++)
    {
        memset(hash, 0, sizeof(hash));
        hash[0] = 1;
        for(int j = 0; j < i; j++)
        {
            hash[sg[j]] = 1;
        }
        for(int j = 0; j < i; j++)
        {
            for(int k = j + 1; k < i; k++)
                hash[sg[j] ^ sg[k]] = 1;
        }
        for(int j = 0; j <MAXN; j++)
        {
            if(hash[j] == 0)
            {
                sg[i] = j;
                break;
            }
        }
    }
    for(int i = 0; i < n; i++) cout <<i << "\t" << sg[i] << endl;
    return 0;
}
结果:

0 1
1 2
2 4
3 7
4 8
5 11
6 13
7 14
8 16
9 19
10 21
11 22
12 25
13 26
14 28
15 31
16 32
17 35
18 37
19 38
20 41
21 42
22 44
23 47
24 49
25 50
26 52
27 55
28 56
29 59
30 61
31 62
32 64
33 67

打表,神一样的可以发现当表示为二进制时1为奇数个则sg[x]=2x,偶数为2x+1


然后注意此题必须要对输入数据去重。。。。

#include<iostream>
#include<cstring>
#include<cstdio>
#include<set>
using namespace std;

int sg(int v)
{
    int p = v;
    int counter = 0;
    while(p > 0)
    {
        counter += (p & 1);
        p >>= 1;
    }
    if(p & 1) return v << 1;
    else return v << 1 | 1;
}

set<int> S;

int main()
{
    //freopen("input.txt", "r", stdin);
    int n, tmp;
    while(scanf("%d", &n) != EOF)
    {
        S.clear();
        if(n == 0) printf("Yes\n");
        else
        {
            scanf("%d", &tmp);
            int ans = sg(tmp);
            S.insert(tmp);
            for(int i = 1; i < n; i++)
            {
                scanf("%d", &tmp);
                if(S.count(tmp) == 0)
                {
                    S.insert(tmp);
                    ans ^= sg(tmp);
                }
            }
            if(ans == 0) printf("Yes\n");
            else  printf("No\n");
        }
    }
    return 0;
}


hdu 3951 升级版打表找规律

首先进行子游戏划分:对于任何连续但头尾不相连的一行硬币,只拿连续个,必然分成2堆连续的子硬币,因此sg函数就可以出来了。

特别说明,题目要求的是成环的,但是任意做一次操作就会变成x-1,x-2,...x-k个不连续,回到上面的sg函数,判断一下这些sg函数里是否有0即可判断NP

打表:

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;

int sg[12][1000];
int a[1000];
int hash[1000];
int k = 1, n = 100;

int main()
{
    freopen("input,txt", "r", stdin);
    sg[k][0] = 0;
    sg[k][1] = 1;
    for(int i = 2; i < 100; i++)
    {
        memset(hash, 0, sizeof(hash));
        for(int j = 0; i-j-1 >= j; j++)
        {
            hash[sg[k][j]^sg[k][i-j-1]] = 1;
        }
        for(int j = 0; j <= n; j++)
        {
            if(hash[j] == 0)
            {
                sg[k][i] = j;
                break;
            }
        }
    }
    for(int i = 0; i < 10; i++)cout << "(" << k <<  ", " << i << ")\t" << sg[k][i] << endl;
    k++; cout << endl;

    sg[k][0] = 0;
    sg[k][1] = 1;
    sg[k][2] = 2;
    for(int i = 3; i < 100; i++)
    {
        memset(hash, 0, sizeof(hash));
        for(int j = 0; i-j-1 >= j; j++)
        {
            hash[sg[k][j]^sg[k][i-j-1]] = 1;
        }
        for(int j = 0; i-j-2 >= j; j++)
        {
            hash[sg[k][j]^sg[k][i-j-2]] = 1;
        }
        for(int j = 0; j <= n; j++)
        {
            if(hash[j] == 0)
            {
                sg[k][i] = j;
                break;
            }
        }
    }
    for(int i = 0; i < 10; i++)cout << "(" << k <<  ", " << i << ")\t" << sg[k][i] << endl;
    k++; cout << endl;

    sg[k][0] = 0;
    sg[k][1] = 1;
    sg[k][2] = 2;
    for(int i = 3; i < 100; i++)
    {
        memset(hash, 0, sizeof(hash));
        for(int j = 0; i-j-1 >= j; j++)
        {
            hash[sg[k][j]^sg[k][i-j-1]] = 1;
        }
        for(int j = 0; i-j-2 >= j; j++)
        {
            hash[sg[k][j]^sg[k][i-j-2]] = 1;
        }
        for(int j = 0; i-j-3 >= j; j++)
        {
            hash[sg[k][j]^sg[k][i-j-3]] = 1;
        }
        for(int j = 0; j <= n; j++)
        {
            if(hash[j] == 0)
            {
                sg[k][i] = j;
                break;
            }
        }
    }
    for(int i = 0; i < 10; i++)cout << "(" << k <<  ", " << i << ")\t" << sg[k][i] << endl;
    k++; cout << endl;
    return 0;
}
结果:

(1, 0) 0
(1, 1) 1
(1, 2) 0
(1, 3) 1
(1, 4) 0
(1, 5) 1
(1, 6) 0
(1, 7) 1
(1, 8) 0
(1, 9) 1

(2, 0) 0
(2, 1) 1
(2, 2) 2
(2, 3) 3
(2, 4) 1
(2, 5) 4
(2, 6) 3
(2, 7) 2
(2, 8) 1
(2, 9) 4

(3, 0) 0
(3, 1) 1
(3, 2) 2
(3, 3) 3
(3, 4) 4
(3, 5) 1
(3, 6) 6
(3, 7) 3
(3, 8) 2
(3, 9) 1
(后略)

可以发现,当k=1时,偶数被必败态。对于原先成环的硬币,必然分为x-1个子硬币,如果x-1为偶数,也就是x为奇数,那么先手必胜。

当k>=2时,有且仅有x=0为必败态。因此,如果n>k,无论走,都是对手的必胜态,所以先手必败。否则,直接全部取走k,即可。

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;

int main()
{
    freopen("input.txt", "r", stdin);
    int n, k;
    int T;
    scanf("%d", &T);
    for(int kase = 1; kase <= T; kase++)
    {
        scanf("%d%d", &n, &k);
        int ans = 0;
        if(k == 1)
        {
            ans = (n & 1);
        }
        else
        {
            if(n <= k) ans = 1;
        }
        printf("Case %d: %s\n", kase, ans > 0? "first":"second");
    }
    return 0;
}

hdu 3389 阶梯博弈

我们将n堆卡片,每一堆视作一个单独的游戏。当n=1,时,先手必败,n=2时,先手必胜,n=3,4,无法移动,同2先手必胜。当n=5,A=5,B=4,此时只能对2、5操作。可以发现先手的sg函数的值等于p(2)^p(5),两堆相同是必败的。

当n>=6,我们可以发现,打表可知,n%6=1,3,4时路径个数必然是偶数,当n%6=0,2,5路径个数必然是奇数

在阶梯博弈中,当存在奇数路径时,答案与偶数路径无关,因为不管对方怎么对偶数做操作,只要把相同个数再往前移一堆即可,因此偶数路径是必败状态。对于单独的某堆奇数路径,我们将其全部移动到偶数路径上,即可成为对方的必败态。因此sg函数是所有奇数路径堆的异或和

阶梯博弈:第1到n层台阶上各有a[i]个石子,要把所有的石子都移动到第0层,每次移动只能从当前层往下移任意到下一层。奇数层做异或和是sg函数(必然存在奇数层)

(注意此题是从n=1开始的!)

#include<iostream>
#include<algorithm>
#include<iomanip>
#include<cstring>
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<string>
#include<vector>
#include<set>
#include<map>
#include<queue>
#include<deque>
#include<stack>
#define clr(a, b) memset(a, b, sizeof(a))
#define sf(a) scanf("%d", &a)
#define sf2(a) scanf("%d%d", &a, &b)
#define sf3(a) scanf("%d%d%d", &a, &b, &c)
#define sfd(a) scanf("%lf", &a)
#define sfs(a) scanf("%s", a)
#define sfgc(a) scanf("%d", &a);getchar()
#define EPS 1e-6
#define INF 0x3f3f3f3f
typedef long long LL;
using namespace std;

const int MAXN = 10000 + 5;
int a[MAXN];

int main()
{
    freopen("input.txt", "r", stdin);
    //ios::sync_with_stdio(false);
    //freopen("output.txt", "w", stdout);
    int T, tmp;
    sf(T);
    for(int kase = 1; kase <= T; kase++)
    {
        int n;
        sf(n);
        for(int i = 1; i <= n; i++) sf(a[i]);
        int ans;
        if(n == 1) ans = 0;
        else
        {
            ans = a[2];
            for(int i = 3; i <= n; i++)
            {
                if(i % 6 == 0 || i % 6 == 2 || i % 6 == 5) ans ^= a[i];
            }
        }
        printf("Case %d: %s\n", kase, ans == 0? "Bob" : "Alice");
    }
    return 0;
}


hdu 3544 打表不平等博弈


具体打表见http://blog.csdn.net/strokess/article/details/52169915

(注意中间HP的计算由于100*1e9必然要用LL)

#include<iostream>
#include<algorithm>
#include<iomanip>
#include<cstring>
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<string>
#include<vector>
#include<set>
#include<map>
#include<queue>
#include<deque>
#include<stack>
#define clr(a, b) memset(a, b, sizeof(a))
#define sf(a) scanf("%d", &a)
#define sf2(a, b) scanf("%d%d", &a, &b)
#define sf3(a, b, c) scanf("%d%d%d", &a, &b, &c)
#define sfd(a) scanf("%lf", &a)
#define sfs(a) scanf("%s", a)
#define sfgc(a) scanf("%d", &a);getchar()
#define EPS 1e-6
#define INF 0x3f3f3f3f
typedef long long LL;
using namespace std;

const int MAXN = 10000 + 5;
int a[MAXN];

int main()
{
    freopen("input.txt", "r", stdin);
    //ios::sync_with_stdio(false);
    //freopen("output.txt", "w", stdout);
    int T, x, y;
    sf(T);
    for(int kase = 1; kase <= T; kase++)
    {
        int n;
        sf(n);
        LL hp = 0;
        for(int i = 0; i < n; i++)
        {
            sf2(x, y);
            while(x > 1 && y > 1)
            {
                x >>= 1;
                y >>= 1;
            }
            hp += x - y;
        }
        //cout << hp << endl;
        printf("Case %d: %s\n", kase, hp <= 0? "Bob" : "Alice");
    }
    return 0;
}


hdu 3863 分析博弈

图是完全对称的,先手有优势,因此必胜。

#include<iostream>
#include<algorithm>
#include<iomanip>
#include<cstring>
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<string>
#include<vector>
#include<set>
#include<map>
#include<queue>
#include<deque>
#include<stack>
#define clr(a, b) memset(a, b, sizeof(a))
#define sf(a) scanf("%d", &a)
#define sf2(a, b) scanf("%d%d", &a, &b)
#define sf3(a, b, c) scanf("%d%d%d", &a, &b, &c)
#define sfd(a) scanf("%lf", &a)
#define sfs(a) scanf("%s", a)
#define sfgc(a) scanf("%d", &a);getchar()
#define EPS 1e-6
#define INF 0x3f3f3f3f
typedef long long LL;
using namespace std;

const int MAXN = 10000 + 5;
int a[MAXN];

int main()
{
    freopen("input.txt", "r", stdin);
    //ios::sync_with_stdio(false);
    //freopen("output.txt", "w", stdout);
    int n;
    while(sf(n) && n != -1)printf("I bet on Oregon Maple~\n");
    return 0;
}

hdu 1517 sg打表找规律

题目不是n个sg函数之和,这种情况我们化繁为简,sg函数取0,1即可。

打表:前闭后开区间

2 1
10 0
19 1
163 0
325 1
2917 0
5833 1
52489 0
104977 1
944785 0

(注意。。。我把打表函数放到了最后的注释里,结果交的时候注释掉的是注释里的freopen不是main里的。。。送了两发罚时,复制提交之前也要检查一遍!)

#include<iostream>
#include<algorithm>
#include<iomanip>
#include<cstring>
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<string>
#include<vector>
#include<set>
#include<map>
#include<queue>
#include<deque>
#include<stack>
#define clr(a, b) memset(a, b, sizeof(a))
#define sf(a) scanf("%d", &a)
#define sfll(a) scanf("%lld", &a)
#define sf2(a, b) scanf("%d%d", &a, &b)
#define sf3(a, b, c) scanf("%d%d%d", &a, &b, &c)
#define sfd(a) scanf("%lf", &a)
#define sfs(a) scanf("%s", a)
#define sfgc(a) scanf("%d", &a);getchar()
#define EPS 1e-6
#define INF 0x3f3f3f3f
typedef long long LL;
using namespace std;

const int MAXN = 10000 + 5;
LL a[100];

int main()
{
    freopen("input.txt", "r", stdin);
    //ios::sync_with_stdio(false);
    //freopen("output.txt", "w", stdout);
    LL p = 2, q = 1e9;
    q *= 400;
    int tot = 0;
    while(p < q)
    {
        a[tot++] = p;
        if(tot & 1) p = (p-1)*9 + 1;
        else p = 2*p - 1;
    }
    LL n;
    while(sfll(n) != EOF)
    {
        for(int i = 0; i < n; i++)
        {
            if(n >= a[i] && n < a[i+1])
            {
                if(i & 1) puts("Ollie wins.");
                else puts("Stan wins.");
                break;
            }
        }
    }
    return 0;
}
/*
const int MAXN = 1000000 + 5;
int sg[MAXN];

int main()
{
    freopen("input.txt", "r", stdin);
    //ios::sync_with_stdio(false);
    freopen("output.txt", "w", stdout);
    sg[1] = 0;
    int n = 1000000;
    for(int i = 2; i < n; i++)
    {
        for(int j = 2; j <= 9; j++)
        {
            if(i % j == 0)
            {
                if(sg[i/j] == 0)
                {
                    sg[i] = 1;
                    break;
                }
            }
            else
            {
                if(sg[i/j + 1] == 0)
                {
                    sg[i] = 1;
                    break;
                }
            }
        }
    }
    for(int i = 2; i < n; i++)
    if(sg[i] != sg[i-1])
        cout << i << '\t' << sg[i] << endl;
    return 0;
}
//*/


hdu 2486 升级斐波那契博弈 k倍动态减法游戏 (模板)

斐波那契博弈:一堆n个的石子,第一次取1-n-1个,第二次开始最多取上一次的2倍,去完最后者胜,称为斐波那契博弈,打表知n=斐波那契数时必败。

此题,第二题开始最多取上次的k倍。

当k=1时,每次取走的不能比上次多,此时打表知必败态是2^k。因为对于必胜态,取走二进制下最后一个1,对方永远取不完。

当k>=3,恩,看不懂,直接抄了放模板上。

/*  HDU 2486 k倍动态减法游戏
n个石子,第一次最多取n-1个,第二次开始最多取上次的k倍,取完win
k=1时,必败态2^k,k=2时必败态斐波那契数,k>=3时见main
k=1,k=2时可以打表,sg[a][b]表示b个石子最多拿k个,打表程序见下
const int MAXN = 10000;
int sg[MAXN][MAXN];
int dfs(int a, int b)
{
	if(sg[a][b] != -1)return sg[a][b];
	if(a >= b) return sg[a][b] = 1;
	for(int i = 1; i <= a; i++)
		if(dfs(2*i, b-i) == 0)return sg[a][b] = 1;
	return sg[a][b] = 0;
}
int main()
{
	clr(sg,-1);
	sg[1][2] = 0;
	sg[0][0] = 0;
	for(int i = 2; i < MAXN; i++)
		if(dfs(i-1, i) == 0) printf("%d\n", i);
	return 0;
}
//*/
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int MAXN = 2000000 + 5;
int a[MAXN],b[MAXN];
int main(){
    int T, n, k, ans;
    scanf("%d",&T);
    for(int kase = 1; kase <= T; kase++){
        scanf("%d%d",&n,&k);
        int i=0,j=0;
        a[0]=b[0]=1;
        while(a[i]<n){
            i++;
            a[i]=b[i-1]+1;
            while(a[j+1]*k<a[i])j++;
            if(a[j]*k<a[i]) b[i]=b[j]+a[i];
            else b[i]=a[i];
        }
        printf("Case %d: ", kase);
        if(a[i]==n) puts("lose");
        else{
            while(n){
                if(n>=a[i]){
                    n-=a[i];
                    ans=a[i];
                }
                i--;
            }
            printf("%d\n",ans);
        }
    }
    return 0;
}

hdu 4315  变形阶梯博弈

先假设王后面没有人。

如果王前面一个都没有,先手必胜

如果王前面只有一个人,紧挨着,先手必败。没有紧挨着,先手紧挨过去,必胜。

如果王前面有两个人,全部紧挨着,第一步直接让最前面的到top,形成2个紧挨状态,必胜。事实上,这只与王和no2紧挨与否有关,当王与no2紧挨,第一步把no1移到top必胜。当王没有与第二个紧挨,不可能把no1移到top,如果no1和no2紧挨…………GG

换思路,假设每个位置是一个单独的游戏,很显然sg(x) = x,然后打出GG

题解:阶梯博弈

先假设n个人取完最后一人胜。从后往前每两个一组。如果n为偶数,这样设置以后,记作sg为中间的空格数,对于每一对,如果紧挨着肯定是必败的,也就是sg(x)=0,那对与对之间就可以看成是一个Nim游戏,取其异或和。这与对之间的距离无关,对于一个必败态,如果我动前者,后者再跟上即可;如果我动后者,导致异或和不为0,也必然存在其他对内距离改变使得异或和再为0。如果n为奇数,第一个人到山顶的距离(包括山顶)视为一个游戏,

而这题,当n为偶数,k在偶数位,也就是每一对的后一个位置,就和上述一毛一样,对整个n/2对做nim和,如果k在奇数位,对上述过程中改变当k前面只有一堆时,那一堆不要跟着倒数第二堆去山顶,而是去第一堆,结果还是和上述一样。但要注意,如果k=1,不存在k前面的堆,此时k直接胜利。

当n为奇数,同理处理,但要注意如果k=2,第一堆的sg要-1,移到1就行,不然直接就是必胜态。k不等于2不用,依旧是个普通的sg函数。

#include<iostream>
#include<algorithm>
#include<iomanip>
#include<cstring>
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<string>
#include<vector>
#include<set>
#include<map>
#include<queue>
#include<deque>
#include<stack>
#define clr(a, b) memset(a, b, sizeof(a))
#define sf(a) scanf("%d", &a)
#define sf2(a, b) scanf("%d%d", &a, &b)
#define sf3(a, b, c) scanf("%d%d%d", &a, &b, &c)
#define sfd(a) scanf("%lf", &a)
#define sfs(a) scanf("%s", a)
#define sfgc(a) scanf("%d", &a);getchar()
#define EPS 1e-6
#define INF 0x3f3f3f3f
#define sfll(a) scanf("%lld", a)
//#define __int64 long long
typedef long long LL;
using namespace std;

const int MAXN = 1000 + 5;
int a[MAXN], sg[MAXN];

int main()
{
    freopen("input.txt", "r", stdin);
    //ios::sync_with_stdio(false);
    //freopen("output.txt", "w", stdout);
    int n, k;
    while(sf2(n, k) != EOF)
    {
        for(int i = 1; i <= n; i++) sf(a[i]);
        int ans = 0;
        if(k == 1) ans = 1;
        else
        {
            if(n & 1)
            {
                for(int i = n; i > 1; i -= 2) sg[(i+1)/2] = a[i] - a[i-1] - 1;
                sg[1] = a[1];
                if(k == 2) sg[1]--;
                ans = sg[(n+1)/2];
                for(int i = 1; i < (n+1)/2; i++)
                    ans ^= sg[i];
            }
            else
            {
                for(int i = n; i > 0; i -= 2) sg[i/2] = a[i] - a[i-1] - 1;
                ans = sg[n/2];
                for(int i = 1; i < n/2; i++)
                    ans ^= sg[i];
            }
        }
        puts(ans != 0 ? "Alice" : "Bob");
    }
    return 0;
}

下面对阶梯博弈做点补充,分别看POJ 1704和蓝桥杯决赛题

poj 1704从后往前两两合一对nim即可

/*
question:
	
*/

#include <iostream>
#include <cstdio>
#include <string>
#include <cstring>
#include <algorithm>
#define sf(a) scanf("%d", &a)
using namespace std;

const int MAXN = 1000 + 5;
int a[MAXN];

int main(){
	freopen("input.txt", "r", stdin);
	int T, n;
	sf(T);
	while(T--)	
	{
		sf(n);
		int ans = 0;
		for(int i = 0; i < n; i++) sf(a[i]);
		sort(a, a + n);
		for(int i = n - 1; i > 0; i -= 2) ans ^= a[i] - a[i-1] - 1;
		if(n & 1)
		{
			ans ^= a[0] - 1;
		}
		puts(ans == 0 ? "Bob will win" : "Georgia will win");
	}
	return 0;
}

蓝桥杯决赛 高僧斗法

(注意:对于求出具体最小解,只能穷举,而且不一定只是移动后者减少nim,甚至是移动前者增加nim也可能是一个解)

#include <iostream>
#include <cstdio>
#include <string>
#include <cstring>
using namespace std;

const int MAXN = 1005;
int a[MAXN];

int main(){
	freopen("input.txt", "r", stdin);
	int T;
	int tot = 0;
	while(scanf("%d", &a[tot]) != EOF) tot++;
	int ans = 0;
	for(int i = 0; i < tot - 1; i+=2) ans ^= a[i+1] - a[i] - 1;
	if(ans == 0) cout << "-1" << endl;
	else
	{
		for(int i = 0; i < tot - 1; i+=2)
		{
			ans ^= (a[i+1] - a[i] - 1);
			int flag = 0;
			for(int p = a[i] + 1; p < a[i+1]; p++)
			{
				if((ans ^ (a[i+1] - p - 1)) == 0)
				{
					cout << a[i] << ' ' << p << endl;
					flag = 1;
					break;
				}
			}
			if(flag)break;
			for(int p = a[i + 1] + 1; p < a[i+2]; p++)
			{
				if((ans ^ (p - a[i] - 1)) == 0)
				{
					cout << a[i+1] << ' ' << p << endl;
					flag = 1;
					break;
				}
			}
			if(flag)break;
			ans ^= (a[i+1] - a[i] - 1);
		}
	}
	return 0;
}

至于hdu3404(nim积),hdu1538(海盗分金),放弃

如果还有其他问题,可以参考http://blog.csdn.net/acm_cxlove/article/details/7854526

至此,博弈论专题完。








补:

2017浙江省赛G题 ZOJ 3964 不平等博弈

对于不平等博弈题目,如hdu3544,正规做法是surrel number,然而身为一个大学生怎么可能看得懂高中生写的论文呢……

所以就分情况贪心讨论

A、B拿石子,B任意拿,但是A不是,对于每堆石子,要么任意拿,要么只能拿奇数,要么只能拿偶数。A先。

首先我们可以得出一个显而易见的观点,B是绝对优势的。
如果全部的石子都是任意拿,就是异或和。
特别的,对于只能拿奇数个的石子,如果其个数是1,也是任意拿,算做异或和里的一种。

如果说存在某一堆只能拿偶数的石子是奇数个,那么A永远取不完这一堆,不管其他石子怎么拿,最后这一堆要么轮到A拿不了,要么被B拿走,必败的。

如果存在两堆只能拿偶数的石子呢,不管A怎么搞,B肯定可以把其中一堆变成奇数个,那A就GG了,这也是必败的。

那么下面讨论取偶数石子的堆数,要么根本没有取偶数石子堆,要么只有一堆个数为偶数的取偶数石子。

如果根本没有偶数堆:(讨论的奇数堆全部都是大于1的)

对于只能拿奇数个的石子,如果它是偶数个石子,A必然一次性拿不完。

将n堆石子看成两部分,一部分是任意取,一部分是奇数取。任意取的部分看sg函数的和。

如果sg=0,奇数取只有一堆,奇数个的。A先手,那一堆全部取走,必胜。

如果sg=0,奇数取只有一堆,偶数个的。不管A去动sg那边,B都可以再变成0,不管A去动偶数那堆变成奇数,B都可以直接把剩下的奇数个全部取走,把sg=0留给A。必败。

如果sg=0,奇数取两堆。不管A去动sg那边,B都可以再变成0,直接忽略。现在有2堆奇数堆给A先手取。

剩下如果两奇数,A取光一堆,B胜,A不取光那堆,变成一奇一偶,B取走奇数全部,B胜。如果两偶数,必败。如果一奇一偶,A去碰奇数,肯定不能取光,剩下俩偶数,B随便取光一个偶数,B胜,A去碰偶数,剩下一奇数一偶数,B取走奇数全部,B胜。说白了一定会有一个偶数状态给A,A必败。

那么sg=0,奇数堆两堆以上,也全是必败。

如果sg不等于0呢,奇数取两堆。此时A还要花功夫去把sg变成0,更不可能赢,所以还是必败。

因此,当奇数堆大于1堆,A必败的。这个对只有一堆偶数取也是同理。

那么到这里,我们只需要再分类讨论奇数堆、偶数堆是0还是1,顶多再加上sg是否等于0,最多8种情况。

综上:

0.如果偶数堆或奇数为2及以上,A必败。

1.如果偶数堆是0,奇数堆也是0,直接看sg函数nim和即可。

2.如果偶数堆是1,奇数堆是0。当偶数堆的那堆个数是奇数时,A必败。当偶数堆那堆是偶数时,若sg=0,A必胜。若sg不等于0,A肯定不可能去不管那堆偶数的,不然被B变成奇数就GG了,取光了也是GG,不取光就被B变成奇数,还是GG,所以A必败。

3.如果偶数堆是0,奇数堆是1。

当奇数堆的那堆个数是偶数时,如果sg=0,A碰奇数堆,B再取光,GG,所以A必败。若sg不等于0,A肯定不会去把sg变成0,也不会把sg变成另一个非0,这样B把sg变成0的话A就GG了,只能A去碰奇数堆变成奇数个,此时似乎不太好判断,因为万一把奇数堆变成1,那么就是NIM和,如果n-1堆的异或和再异或1是0,那么A直接把奇数取变成1,P状态给了B,A必胜,不然的话,当这个NIm^1不是0,A肯定不能变成1,只能变成3,5,7...,B只要再把这堆石子变成2,A就必败了,因此这里是要根据n-1的Nim和^1来讨论。

当奇数堆得那堆个数是奇数时,如果sg=0,A取走奇数堆,B变成P,A必胜。如果sg不等于0,A肯定不会去把sg变成0,也不可能拿光奇数堆,也不可能把奇数堆变成偶数堆因为这样B再变成sg=0时A就GG了,只可能去把sg变成另外一个非0,且这个操作以后,n个异或和必须为0,不然B就是N状态了。那么可以根据这2个式子可以得出,调整完后的sg和原奇数堆的那个奇数相等,不然没法使得整体异或和为0。如果这个那个奇数非常大,这样是做不到的,A必败。如果那个奇数很小,但是B只需要将n-1个异或和调整为奇数的个数-2,那么A再也无法做到整体异或和为0,所以A必败。

4.如果偶数堆、奇数堆各是1,其中偶数堆是偶数,奇数堆的数大于1。

A先手必然要去碰偶数堆,不然被B变成奇数死都赢不了。而且只能取光,剩2,4,6直接被变成奇数还是死。而对B来说,只要n个异或和不为0,就是必胜的。现在B面对的是n-2堆sg和一堆奇数取,那只可能现在的整体异或和为0,也就是奇数堆那个和剩下n-2个nim和相等,从上面的讨论可以知道最多三步A必死。因此这种情况A必败。

其实4和0可以归结为奇数取+偶数取不能大于1。

#include<iostream>
#include<algorithm>
#include<iomanip>
#include<cstring>
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<string>
#include<vector>
#include<set>
#include<map>
#include<queue>
#include<deque>
#include<stack>
#define clr(a, b) memset(a, b, sizeof(a))
#define sf(a) scanf("%d", &a)
#define sf2(a, b) scanf("%d%d", &a, &b)
#define sf3(a, b, c) scanf("%d%d%d", &a, &b, &c)
#define sfd(a) scanf("%lf", &a)
#define sfs(a) scanf("%s", a)
#define sfgc(a) scanf("%d", &a);getchar()
#define EPS 1e-6
#define PI acos(-1.00)
#define INF 0x3f3f3f3f
//#define __int64 long long
//#define sfll(a) scanf("%lld", &a)
//#define sfll(a) scanf("%I64d", &a)
//typedef long long LL;
using namespace std;

const int MAXN = 100000 + 5;
int a[MAXN];

int main()
{
    //freopen("B.txt", "r", stdin);
    //ios::sync_with_stdio(false);
    //freopen("output.txt", "w", stdout);
    int T;
    sf(T);
    while(T--)
    {
        int n, tmp;
        sf(n);
        for(int i = 0; i < n; i++) sf(a[i]);
        int nim = 0;
        int p1 = 0, p2 = 0, p;
        for(int i = 0; i < n; i++)
        {
            nim ^= a[i];
            sf(tmp);
            if(tmp == 1 && a[i] > 1)p1++, p = i;
            if(tmp == 2) p2++, p = i;
        }
        if(p1 + p2 > 1) puts("Bob");
        else
        {
            if(p1 + p2 == 0) puts(nim == 0 ?  "Bob": "Alice");
            else
            {
                nim ^= a[p];
                if(p1 == 1) puts((nim == 0 && a[p] % 2 == 1) || ((nim^1) == 0 && a[p] % 2 == 0) ?  "Alice":  "Bob");
                else puts(nim == 0 && a[p] % 2 == 0 ? "Alice":  "Bob");
            }
        }
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值