博弈题目(二)

博弈题目(二)

HDU - 1536 S-NIM

S-NIM

思路

我看了半天什么XOR的,最后告诉我不用这个信息?光理解题意理解半天,最后发现是最基本的SG函数。不过也遇到坑了,就是f[]数组一定要排序,是递增序列。OJ后台输入和输出的数据是分开的,所以输出结果可以一个字符一个字符输出。

预处理打表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include <bits/stdc++.h>
using namespace std;
int sg[10001];
bool vis[10001];
int f[101], t;
void getSG()
{
for(int i = 1; i <= 10000; ++i)
{
memset(vis, 0 ,sizeof(vis));
for(int j = 0; j < t && f[j] <= i; ++j)
{
vis[sg[i - f[j]]] = 1;
}
for(int j = 0;; ++j)
{
if(!vis[j])
{
sg[i] = j;
break;
}
}
}
}

int main()
{
int m, num, n;
while(cin >> t && t)
{
for(int i = 0; i < t; ++i)
cin >> f[i];
sort(f , f + t);
getSG();
cin >> m;
for(int i = 0; i < m; ++i)
{
cin >> n;
int flag = 0;
for(int j = 0; j < n; ++j)
{
cin >> num;
flag ^= sg[num];
}
if(flag)
cout << 'W';
else
cout << 'L';
}
cout << '\n';
}
}

dfs 借鉴师哥写的,只是把这个模板记住了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#include <bits/stdc++.h>
using namespace std;
int sg[10011];
int f[101], t , m ,n, num;
int dfsSG(int x)
{
if(sg[x] != -1)
return sg[x];

bool vis[101] = {0};
for(int i = 0; i < t && f[i] <= x; ++i)
{
dfsSG(x - f[i]);
vis[sg[x - f[i]]] = 1;
}
for(int i = 0; ;++i)
{
if(!vis[i])
{
return sg[x] = i;
}
}
}

int main()
{
while(cin >> t && t)
{
for(int i = 0; i < t; ++i)
cin >> f[i];
sort(f , f + t);
memset(sg, -1 , sizeof(sg));
cin >> m;
while(m--)
{
cin >> n;
int flag = 0;
for(int j = 0; j < n; ++j)
{
cin >> num;
flag ^= dfsSG(num);
}
if(flag)
cout << 'W';
else
cout << 'L';
}
cout << '\n';
}
}

HDU - 1527 取石子游戏

取石子游戏

思路

典型的威佐夫博弈,真的很奇妙。

学习网址: 威佐夫博弈

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <bits/stdc++.h>
using namespace std;

int main()
{
int n, m;
while(cin >> n >> m)
{
int num = abs(n - m);
int num1 = (int)(((sqrt(5) + 1 )/ 2.0) * num);
if(num1 == min(n , m))
cout << 0 << '\n';
else
cout << 1 << '\n';
}
}

HDU 2177

取两堆石子

思路

应用威佐夫博弈的思想,不断枚举,判断取完之后是否构成了奇异状态。忘了在赢的时候写1,一直WA。注意输出的格式,我用set存的。这题还有公式解法,以后再补吧。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#include <bits/stdc++.h>
using namespace std;

int elect(int n, int m )
{
int num = int((sqrt(5) + 1)/ 2.0 * abs(n - m));
if(num == min(n , m))
return 1;
return 0;
}
int main()
{
int n, m;
while(cin >> n >> m && n && m){
set<pair<int,int> >S;
set<pair<int,int > >::iterator it;
int num = elect(n , m);
if(num)
{
cout << 0 << '\n';
continue;
}
cout << 1 << '\n';
for(int i = 0; i <= min(n , m); ++i)
{
int num1 = elect(n - i, m - i);
if(num1)
{
cout << min(n - i, m - i) << ' ' << max(n - i, m - i) << '\n';
}
}
for(int i = 0; i <= n; ++i)
{
int num1 = elect(n - i, m);
if(num1)
{
if(S.count({min(n - i , m), max(n - i, m)}) == 0)
{
cout << min(n - i, m) << ' ' << max(n - i, m) << '\n';
S.insert({min(n - i, m), max(n - i, m)});
}
}
}
for(int i = 0; i <= m; ++i)
{
int num1 = elect(n, m - i);
if(num1)
{
if(S.count({min(n , m - i), max(n , m - i)}) == 0)
{
cout << min(n , m - i) << ' ' << max(n , m - i) << '\n';
S.insert({min(n , m - i), max(n , m - i)});
}
}
}
}
}

HDU 1850

Being a Good Boy in Spring Festival

思路

考察NIM博弈的理解。如果堆数的异或和为0直接输出0. 否则有 a1^a2^a3^an = k != 0 肯定存在某个ai改变成为ai’ 后,使得 a1^a2^ai‘^an = 0. 我们现在要看看有多少个ai符合这样的条件。看了网上的题解,感觉说的有些模糊,于是我自己又推了一遍。

a1^a2^a3^ai^an = k != 0 //可以得到a1 ^ a2 ^ a3 ^ an(不包括ai) = k^ai

//当我们把ai 变为 ai’ 后

a1^a2^a3^ai’^an = 0 //把上边得到的式子带进来 -> k^ai = ai’

因此只要满足k ^ ai = ai’ 就说明我们可以把ai 变为 ai’后 就有异或和为0的局面了。 而这个ai’只要满足 ai’ <= ai即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <bits/stdc++.h>
using namespace std;
int a[101];
int main()
{
int n;
while(cin >> n && n)
{
int sum = 0;
for(int i = 0; i < n ; ++i)
{
cin >> a[i];
sum ^= a[i];
}
if(sum == 0)
{
cout << "0" << '\n';
continue;
}
int cnt = 0;
for(int i = 0; i < n; ++i)
{
int num1 = sum^a[i];
if(num1 < a[i])
{
cnt++;
}
}
cout << cnt << '\n';
}
}