数论 01_博弈论

Nim游戏

https://www.acwing.com/problem/content/893/

分析:

谁进行最后一次,谁就赢。那么先手面对异或和为0就是必败局面(因为后手已经进行到了必胜局面,将最后的石子给那完了)

所以,异或和为0,先手必输,否则,先手必胜。

代码:

 1 #include <iostream>
 2 
 3 using namespace std;
 4 
 5 int ans, n, x;
 6 int main()
 7 {
 8     cin >> n;
 9     while (n -- )
10     {
11         cin >> x;
12         ans ^= x;
13     }
14     if (ans) cout << "Yes" << "
";
15     else cout << "No" << "
";
16     return 0;
17 }
View Code

 台阶-Nim游戏

链接:https://www.acwing.com/problem/content/894/

分析:

我们可以在任意台阶上,拿任意颗石子放到它的下一个台阶,但拿到地面上的石子就不能再拿了。谁无法操作谁输(谁最后一个拿,谁赢)。

结论为:若奇数台阶上的石子异或和为0,则先手必败,否则,先手必胜。

证明:如果异或和不为0, 先手第一次拿石子后,将局面变为异或和为0,再那之后,先手总是将必败局面留给了后手(异或和为0,为必败局面,因为当你面对异或和为0,到了结局,队手将石子全放到了地面上)。

  如果先手已经给后手造成了必败局面,那么后手如果拿偶数上的石子放到奇数层,我们将这些石子再拿到下一个偶数层。如果后手拿了奇数上的石子,有Nim游戏(上一题),使奇数层异或和为x,我们可以找到一个a[i]从中拿出(a[i] - x)个石子。

使局面再次变为必败局面留给后手。

思考:

为什么不能采用偶数层异或和呢?

现在,假设我们处于必胜局面,我们通过拿出若干个石子,试的局面变成了必败局面,也就是偶数层异或和为0,这时,后手将第一层(这是奇数层)的石子,全部拿到地面上,!!!注意,这时,局面还是必败局面,先手却面临了必败局面,因为后手将第一层放到地面上,对我们的偶数层异或和没有影响,还是异或和为0,但先手却面临着必败局面,而且还不能改变局面,因为第一层已经没有了石子了,这个时候,先手只能破坏异或和为0的局面,

代码:

 1 #include <iostream>
 2 
 3 using namespace std;
 4 
 5 // const int N = 
 6 
 7 int main()
 8 {
 9     int ans = 0;
10     int n; cin >> n;
11     for (int i = 1; i <= n; i ++ )
12     {
13         int x; scanf("%d", &x);
14         if (i & 1) ans ^= x;
15     }
16     if (ans) cout << "Yes" << "
";
17     else cout << "No" << "
";
18     
19     return 0;
20 }
View Code

 集合-Nim游戏

链接:https://www.acwing.com/problem/content/895/

 分析:

用到了SG函数

如果SG(x)等与0,一定是一个必败局面,

 SG(x)为0,先手必败,SG(x)不为0,先手必胜

可以以这个图为例 。

如果n副图的异或和为0的话,则是一个先手必败局面,否则就是一个先手必胜局面,这已经不需要再分析了。

终止状态定义为0,其他所有sg的值定义成当前状态不能到达的状态的最小自然数是多少。

代码:

 1 #include <bits/stdc++.h>
 2 
 3 using namespace std;
 4 
 5 const int N = 110, M = 10010;
 6 
 7 int n, k;
 8 int a[N], f[M];
 9 
10 int sg(int x)
11 {
12     if (f[x] != -1) return f[x];
13     
14     unordered_set<int> st;
15     for (int i = 1; i <= k; i ++ )
16     {
17         int sum = a[i];
18         if (x >= sum) st.insert(sg(x - sum));
19     }
20     
21     for (int i = 0; ; i ++ )
22         if (!st.count(i)) return f[x] = i;
23 }
24 int main()
25 {
26     cin >> k;
27     for (int i = 1; i <= k; i ++ )
28     {
29         cin >> a[i];
30     }
31     
32     int ans = 0;
33     cin >> n;
34     memset(f, -1, sizeof f);
35     for (int i = 0; i < n; i ++ )
36     {
37         int x; cin >> x;
38         ans ^= sg(x);
39     }
40     
41     if (ans) cout << "Yes" << "
";
42     else cout << "No" << "
";
43     
44     return 0;
45 }
View Code

拆分-Nim游戏

链接: https://www.acwing.com/problem/content/896/

分析:

这题目题意不清。。。

题目说是,可以从一个大的堆里面拿出若干个,可以放入两个更小的石堆里面,而且还能多放点,比如从大堆里拿了5个,可以向一堆放5个,可以给另一堆也放5个,放4个,... ,放0个。

代码:

 1 #include <bits/stdc++.h>
 2 
 3 using namespace std;
 4 
 5 const int N = 110;
 6 
 7 int n;
 8 int f[N];
 9 
10 int sg(int x)
11 {
12     if (f[x] != -1) return f[x];
13     
14     unordered_set<int> st;
15     for (int i = 0; i < x; i ++ )
16         for (int j = 0; j <= i; j ++ )
17             st.insert(sg(i) ^ sg(j));
18     
19     for (int i = 0; ; i ++ ) 
20         if (!st.count(i)) //count(x)返回x的个数
21             return f[x] = i;
22 }
23 int main()
24 {
25     cin >> n;
26 
27     memset(f, -1, sizeof f);    
28     int ans = 0;
29     while (n -- )
30     {
31         int x; cin >> x;
32         ans ^= sg(x);
33     }
34     
35     if (ans) cout << "Yes" << "
";
36     else cout << "No" << "
";
37     
38     return 0;
39 }
View Code

移棋子游戏

链接:https://www.acwing.com/problem/content/1321/

分析:

则就是sg函数的模板题了。

只需要将每个棋子的sg函数求出来异或一下就OK了。

代码:

 1 #include <iostream>
 2 #include <unordered_set>
 3 #include <cstring>
 4 
 5 using namespace std;
 6 
 7 const int N = 2010, M = 6010;
 8 
 9 int n, m, k;
10 int h[N], e[M], ne[M], tot;
11 int f[M];
12 
13 void add(int a, int b)
14 {
15     e[++ tot] = b, ne[tot] = h[a], h[a] = tot; 
16 }
17 
18 int sg(int x)
19 {
20     if (f[x] != -1) return f[x];
21     
22     unordered_set<int> st;
23     for (int i = h[x]; ~i; i = ne[i])
24     {
25         int y = e[i];
26         st.insert(sg(y));
27     }
28     
29     for (int i = 0; ; i ++ )
30         if (!st.count(i)) return f[x] = i;
31 }
32 
33 int main()
34 {
35     cin >> n >> m >> k;
36     
37     memset(h, -1, sizeof h);
38     memset(f, -1, sizeof f);
39     for (int i = 0; i < m; i ++ )
40     {
41         int x, y; cin >> x >> y;
42         add(x, y);
43     }
44     
45     int ans = 0;
46     for (int i = 0; i < k; i ++ )
47     {
48         int x; cin >> x;
49         ans ^= sg(x);
50     }
51     
52     if (ans) cout << "win" << "
";
53     else cout << "lose" << "
";
54     
55     return 0;
56 }
View Code

取石子【难】

链接:https://www.acwing.com/problem/content/1323/

分析:

完全不知道该怎么想,正解是,让a表示只有一颗石子的堆数,让b表示每堆石子数大于1个的  石子数+堆数-1.

然后,必胜局面是b为奇数。

按照yxc说法,奇数状态必存在一种方式,到达偶数状态,而偶数状态只能转向奇数状态,最后,先手面对一堆一个石子,就赢了。

分为五种情况:用f[a][b]表示关系

f[a-1][b],从a中取出一个石子(a大于0)

f[a][b-1],从b中取出一个石子(b大于0)

f[a][b-1],和并b中两堆(b大于1)

f[a-2][b+3],合并a中两个(a大于1)

f[a-1][b+1],合并a中的一个和b中的一堆

代码:

 1 #include <cstdio>
 2 #include <cstring>
 3 #include <iostream>
 4 using namespace std;
 5 
 6 const int N = 55, M = 50050;
 7 
 8 int f[N][M];
 9 
10 int dp(int a, int b)
11 {
12     int &x = f[a][b];
13     if (x != -1) return x;
14     if (!a) return x = b % 2;//a等于0,只要看b是否为奇数就好了
15     if (b == 1) return dp(a + 1, 0);
16     //b只有一堆,这一堆里只有一个,所以,它可以算入一个a了
17     
18     if (a && !dp(a - 1, b)) return x = 1;
19     if (b && !dp(a, b - 1)) return x = 1;
20     if (a >= 2 && !dp(a - 2, b + (b ? 3 : 2))) return x = 1;
21     if (a && b && !dp(a - 1, b + 1)) return x = 1;
22     
23     return x = 0;
24 }
25 int main()
26 {
27     memset(f, -1, sizeof f);
28     
29     int T; cin >> T;
30     while (T -- )
31     {
32         int n; cin >> n;
33         int a = 0, b = 0;
34         for (int i = 0; i < n; i ++ )
35         {
36             int x; cin >> x;
37             if (x == 1) a ++;
38             else b += b ? x + 1 : x;//第一次,一堆,x个,其他,加一个堆数,加x的个数
39         }
40         
41         if (dp(a, b)) cout << "YES" << "
";
42         else cout << "NO" << "
";
43     }
44     
45     return 0;
46 }
View Code

取石子游戏【超难】

链接:https://www.acwing.com/problem/content/1324/

捡石头:

链接:https://ac.nowcoder.com/acm/problem/14388

分析:

谁是最后一个,谁就赢。

 如果n % (m + 1) == 0,那么先手必败,因为不管先手拿多少石头,后手都可以凑出来m + 1。这样,一直到最后。

如果n % (m + 1) != 0,那么后手必败,因为第一次先手先拿,然后后手再拿,然后先手就可以凑出来m+1,然后持续到最后。

代码:

 1 #include <iostream>
 2 #include <cstring>
 3 #include <cstdio>
 4 #include <algorithm>
 5 #include <cmath>
 6 #include <iomanip>
 7 #include <limits.h>
 8 #include <sstream>
 9 #include <cctype>
10 #include <numeric>
11 #include <vector>
12 #include <queue>
13 #include <deque>
14 #include <stack>
15 #include <map>
16 #include <unordered_map>
17 #include <unordered_set>
18 #include <set>
19 //#pragma GCC optimize(2)
20 //#pragma GCC optimize(3, "Ofast", "inlin")
21 
22 using namespace std;
23 
24 #define ios ios::sync_with_stdio(false) , cin.tie(0)
25 #define x first
26 #define y second
27 
28 typedef long long LL;
29 typedef unsigned long long ULL;
30 typedef pair<int, int> PII;
31 
32 const int N = 100010, INF = 0x3f3f3f3f, mod = 1e9 + 7, base = 131;
33 const double eps = 1e-6, PI = acos(-1);
34 
35 int n, m;
36 
37 void work()
38 {
39   cin >> n >> m;
40 
41   if (n <= m) puts("first");
42   else 
43   {
44     if (n % (m + 1) == 0) puts("second");
45     else puts("first");
46   }  
47 }
48 
49 int main()
50 {
51   //ios;
52   int T = 1;
53   // cin >> T;
54   while (T -- )
55   {
56     work();
57   }
58 
59   return 0;
60 }
View Code

小牛vs小客:

https://ac.nowcoder.com/acm/problem/15065

分析:

当n小于等于2的时候,很显然,XiaoNiu一定会赢,但是当n大于2的时候,不管先手怎么办,后手都可以将剩下的石子分为偶数部分,所以后手必赢

代码:

 1 #include <iostream>
 2 #include <cstring>
 3 #include <cstdio>
 4 #include <algorithm>
 5 #include <cmath>
 6 #include <iomanip>
 7 #include <limits.h>
 8 #include <sstream>
 9 #include <cctype>
10 #include <numeric>
11 #include <vector>
12 #include <queue>
13 #include <deque>
14 #include <stack>
15 #include <map>
16 #include <unordered_map>
17 #include <unordered_set>
18 #include <set>
19 //#pragma GCC optimize(2)
20 //#pragma GCC optimize(3, "Ofast", "inlin")
21 
22 using namespace std;
23 
24 #define ios ios::sync_with_stdio(false) , cin.tie(0)
25 #define x first
26 #define y second
27 
28 typedef long long LL;
29 typedef unsigned long long ULL;
30 typedef pair<int, int> PII;
31 
32 const int N = 100010, INF = 0x3f3f3f3f, mod = 1e9 + 7, base = 131;
33 const double eps = 1e-6, PI = acos(-1);
34 
35 
36 void work()
37 {
38   int n;
39   while (cin >> n)
40   {
41     if (n <= 2) cout << "XiaoNiu" << "
";
42     else cout << "XiaoKe" << "
";
43   }
44 }
45 
46 int main()
47 {
48   //ios;
49   int T = 1;
50   // cin >> T;
51   while (T -- )
52   {
53     work();
54   }
55 
56   return 0;
57 }
View Code
栗酱的异或和

分析:

NIM游戏,这里,第k堆是先要选的,也是必选的,是先手要选的。同时,谁先不能取谁就输了,也就是谁最后一个取,谁就赢了。

如果不考虑第一次要选k的话,异或和为0,先手必败(所有物品都被取光是一个必败局面,因为队友取走了最后一件物品,已经胜利了)。

那么这个时候,加上先选第k个物品的限制。

我们可以假设第一次取第k堆时,没有取,留到最后先手再取。

这样,我们将除了第k堆以外的石子异或起来,假设结果为x的话,这是就是先手所面临的局面,要将第k堆里取出若干个,将这x个石子全部消掉,消掉的话,就意味着,最后一次是先手拿完的(x=0的话,先手也要拿,因为先手必须要拿第k堆至少一个石子,不能不拿,这个时候,可以将第k堆全部拿了就可以了)。但是,如果x大于第k堆的数量,就没办法拿了,当x等于第k堆的数量的时候,

这样,我们将除了第k堆以外的石子异或起来,假设结果为x的话,我们的目的就是让异或的结果不为0,所以,如果第k堆的石子数大于x的话,那么,我们去掉a[k] - x个石子就可以了,先手拿掉(a[k]-x)个石子,让所有石子的异或和为0,将必败局面丢给了后手。

代码:

 1 #include <iostream>
 2 #include <cstring>
 3 #include <cstdio>
 4 #include <algorithm>
 5 #include <cmath>
 6 #include <iomanip>
 7 #include <limits.h>
 8 #include <sstream>
 9 #include <cctype>
10 #include <numeric>
11 #include <vector>
12 #include <queue>
13 #include <deque>
14 #include <stack>
15 #include <map>
16 #include <unordered_map>
17 #include <unordered_set>
18 #include <set>
19 //#pragma GCC optimize(2)
20 //#pragma GCC optimize(3, "Ofast", "inlin")
21 
22 using namespace std;
23 
24 #define ios ios::sync_with_stdio(false) , cin.tie(0)
25 #define x first
26 #define y second
27 
28 typedef long long LL;
29 typedef unsigned long long ULL;
30 typedef pair<int, int> PII;
31 
32 const int N = 100010, INF = 0x3f3f3f3f, mod = 1e9 + 7, base = 131;
33 const double eps = 1e-6, PI = acos(-1);
34 
35 int n, k;
36 int a[N];
37 
38 void work()
39 {
40   cin >> n >> k;
41   int ans = 0;
42   for (int i = 1; i <= n; i ++ )
43   {
44     cin >> a[i];
45     if (i == k) continue;
46     ans ^= a[i];
47   }
48   // cout << ans << endl;
49   // cout << (ans ^ a[k]) << "
";
50   if (ans < a[k]) cout << "Yes" << "
";
51   else cout << "No" << "
";
52 }
53 
54 int main()
55 {
56   //ios;
57   int T = 1;
58   cin >> T;
59   while (T -- )
60   {
61     work();
62   }
63 
64   return 0;
65 }
View Code
原文地址:https://www.cnblogs.com/Iamcookieandyou/p/14668809.html