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;
}
#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;
}
#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,结论需要记住!
#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;
}
下面是另外一种解法:
#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;
}
若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;
}
#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;
}
#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;
}
#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;
}
如果有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;否则………………没法分析,陷入瓶颈
#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;
}
打表结果:
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
#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+线段树
#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;
}
其结果:
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
#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;
}
#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;
}