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;
}

每日打卡 2017.04.02 博弈论专题相关推荐

  1. 2017.04.02【NOIP 普及组】模拟赛C组 T1:区间

    区间 题目描述 给定n个闭区间 [ai,bi], i=1,2,-,n. 这些区间的和可以用两两不相交的闭区间的和来表示.你的任务是找到这样的区间数目最少的表示,且把它们按升序的方式写到输出文件中.当且 ...

  2. 每日打卡 2017.03.15 北大信科2017机试真题题解

    注:除最后一题外均已在原题出处AC 题库链接:http://jmusoft.openjudge.cn/beida/ 1.奖学金 总时间限制:  1000ms  内存限制:  65536kB 描述 某小 ...

  3. 天梯赛每日打卡04(26-40题解)

    文章目录 前言 L1-026 I Love GPLT (5 分) L1-029 是不是太胖了 (5 分) L1-036 A乘以B (5 分) L1-038 新世界 (5 分) L1-040 最佳情侣身 ...

  4. 2022-01-10 每日打卡:难题精刷

    2022-01-10 每日打卡:难题精刷 写在前面 "这些事儿在熟练之后,也许就像喝口水一样平淡,但却能给初学者带来巨大的快乐,我一直觉得,能否始终保持如初学者般的热情.专注,决定了在做某件 ...

  5. 2022-01-22 每日打卡:难题精刷

    2022-01-22 每日打卡:难题精刷 写在前面 "这些事儿在熟练之后,也许就像喝口水一样平淡,但却能给初学者带来巨大的快乐,我一直觉得,能否始终保持如初学者般的热情.专注,决定了在做某件 ...

  6. python pandas dataframe 行列选择,切片操作 原创 2017年02月15日 21:43:18 标签: python 30760 python pandas dataframe

    python pandas dataframe 行列选择,切片操作 原创 2017年02月15日 21:43:18 标签: python / 30760 编辑 删除 python pandas dat ...

  7. 闲来无事刷水题、简单博弈论专题、sg函数、洛谷

    记 今天闲来无事,不想刷codeforces了,到洛谷提高组训练营找几道水题刷着玩玩(虽然自己早已过了打OI的年纪)- 简单博弈论专题 P1199 三国游戏 这么考虑,由于电脑总是不能让我搭配出当前能 ...

  8. 从入门到入土:python爬虫|SCU每日打卡自动填写|测试训练|

    此博客仅用于记录个人学习进度,学识浅薄,若有错误观点欢迎评论区指出.欢迎各位前来交流.(部分材料来源网络,若有侵权,立即删除) 本人博客所有文章纯属学习之用,不涉及商业利益.不合适引用,自当删除! 若 ...

  9. P1664 每日打卡心情好

    P1664 每日打卡心情好 提交29.87k 通过11.90k 时间限制1.00s 内存限制125.00MB 提交答案加入题单复制题目 做题计划(首页) 个人题单 团队题单 保存 选择团队 保存 题目 ...

最新文章

  1. java 文件下载 【学习记录】
  2. asp.net中将数据库绑定到DataList控件的实现方法与实例代码
  3. 单片机GPIO软件模拟I2C通讯程序
  4. Python知识:关于map
  5. 类似纪念碑谷的unity2d素材包_有哪些免费的音效素材网站?
  6. 在linux文件共享接口,入坑Linux-day13(使用vsftpd服务传输文件、使用Samba或NFS实现文件共享)...
  7. tcp建立连接为什么需要三次握手
  8. Digit sum【暴力+打表】
  9. AngularJs-指令和指令之间的交互(动感超人)
  10. 报道称奈雪的茶通过港交所聆讯 回应:以公司经监管机构批准的公告为准
  11. mxnet导入图像数据
  12. 小米平板android版本,小米平板2发布:分Android和Wind 10两个版本
  13. 身份证合法验证查询易语言代码
  14. java osm_OSM初识(三)OSM Data
  15. [BZOJ3895]取石子
  16. ImportError: No module named 'win32api'
  17. Win10 Windows Defender 保护历史记录清空方法
  18. java猜单词游戏_Java_初级编程,猜英文单词游戏
  19. 【java笔记】day01
  20. C#通过SendMessage消息来发送接收文本消息设定控件text

热门文章

  1. 在NVIDIA 官网下载驱动和Cuda库太慢或失败的解决办法
  2. 故障电路指示器(FCI)的全球与中国市场2022-2028年:技术、参与者、趋势、市场规模及占有率研究报告
  3. 七年级计算机期末考试题带答案,七年级信息技术试题及答案
  4. 手写字体识别(3) 训练及测试
  5. SDCC 的 MCS-51 汇编基础概念和传参方式
  6. 如何优雅地赞美他人?不妨尝试下这几种方式
  7. 计算机二级c考试江苏卷,2011年春江苏省计算机二级C考试笔试真题及答案
  8. 【巨杉数据库SequoiaDB】巨杉数据库与浪潮商用机器完成技术兼容互认证
  9. Connection reset by peer (秒懂)
  10. 用python画猫和老鼠_利用python如何实现猫捉老鼠小游戏