[刷题] 博弈论(SG函数)

SPOJ9934 Alice-Alice and Bob

Description

Alice和Bob两个好朋友又开始玩取石子了。
游戏开始时,有N堆石子排成一排,然后他们轮流操作(Alice先手),每次操作时从下面的规则中任选一个:

  • 从某堆石子中取走一个
  • 合并任意两堆石子

不能操作的人输。Alice想知道,她是否能有必胜策略。
数据范围 \(1\le T\le 4000, 1\le n\le 50, 1\le A_i\le 1000\)

Solution

这题烦就烦在有石子数为\(1\)的堆。所以我们考虑记搜:
\(f[i][j]\) 表示有 \(i\) 个1,大于 \(1\) 的堆有 \(j\) 次操作的\(SG\)函数值。
那么,显然可以转移,具体参考代码。
复杂度 \(O(n^2 值域)\)

Code

// Author: wlzhouzhuan
#pragma GCC optimize(2)
#pragma GCC optimize(3)
#include <bits/stdc++.h>
using namespace std;
 
#define ll long long
#define ull unsigned long long
#define rint register int
#define rep(i, l, r) for (rint i = l; i <= r; i++)
#define per(i, l, r) for (rint i = l; i >= r; i--)
#define mset(s, _) memset(s, _, sizeof(s))
#define pb push_back
#define pii pair <int, int>
#define mp(a, b) make_pair(a, b)
 
inline int read() {
  int x = 0, neg = 1; char op = getchar();
  while (!isdigit(op)) { if (op == '-') neg = -1; op = getchar(); }
  while (isdigit(op)) { x = 10 * x + op - '0'; op = getchar(); }
  return neg * x;
}
inline void print(int x) {
  if (x < 0) { putchar('-'); x = -x; }
  if (x >= 10) print(x / 10);
  putchar(x % 10 + '0');
}
 
const int N = 50005;

int f[51][N], n;
int SG(int a, int b) {
  if (~f[a][b]) return f[a][b]; 
  if (!a) return f[a][b] = b % 2; // 如果全是>1的堆,那么判断奇偶即可 
  if (b == 1) return f[a][b] = SG(a + 1, 0); // 这是一个=1的堆,移回去 
  int t = 2;
  if (b) t = min(t, SG(a, b - 1)); // 取走一个>1的堆的元素
  if (a) t = min(t, SG(a - 1, b)); // 取走一个1的堆中的元素
  if (a > 1) t = min(t, SG(a - 2, b + 2 + (b ? 1 : 0))); // 合并两个1的堆,如果有>1的堆,则新增一个可合并次数 
  if (a && b) t = min(t, SG(a - 1, b + 1)); // 将1的堆合并到>1的堆中
  if (!t) f[a][b] = 1;
  else f[a][b] = 0;
  return f[a][b];    
}
int main() {
  int T = read();
  mset(f, -1);
  for (int _ = 1; _ <= T; _++) {
    //mset(f, -1);
    n = read();
    int a1 = 0, tot = 0;
    for (int i = 1; i <= n; i++) {
      int x = read();
      if (x == 1) a1++;
      else tot += x + 1;
    }
    if (tot) tot--; // 堆数 - 1 = 可合并次数
    printf("Case #%d: ", _);
    if (SG(a1, tot)) puts("Alice");
    else puts("Bob"); 
  }
  return 0;
}

BZOJ1299 巧克力棒

Description

TBL和X用巧克力棒玩游戏。每次一人可以从盒子里取出若干条巧克力棒,或是将一根取出的巧克力棒吃掉正整数长度。TBL先手两人轮流,无法操作的人输。 他们以最佳策略一共进行了10轮(每次一盒)。你能预测胜负吗?
数据范围 \(T=10,1\le n\le 14, 1\le L\le 1e9\)

Solution

我们从这 \(n\) 根巧克力棒中选出 \(m(m>0)\) 根,如果这 \(m\) 根巧克力棒的\(xor\)和为\(0\),并且剩下的 \(n-m\) 根巧克力棒无论怎么取,异或和均不为\(0\),那么这种情况必胜。
事实上,我们只需要判断能否挑出一些巧克力棒,它们的异或和为\(0\)即可。
因为,假设有好多组数都可以做到异或和为\(0\),那么我们一定会把这几组都选上。故只要存在异或和为\(0\),那么说明存在至少一组满足条件,那么剩下的我能取的尽可能取即可。
其实这题可以用线性基维护,但是\(n\)这么小,直接状压枚举即可。
复杂度 \(O(2^n)\),用线性基可做到 \(O(n log值域)\)
emmm 值得一提的是,这题如果必胜要输出NO,必败输出YES(出题人有毒吧..)

Code

// Author: wlzhouzhuan
#pragma GCC optimize(2)
#pragma GCC optimize(3)
#include <bits/stdc++.h>
using namespace std;
 
#define ll long long
#define ull unsigned long long
#define rint register int
#define rep(i, l, r) for (rint i = l; i <= r; i++)
#define per(i, l, r) for (rint i = l; i >= r; i--)
#define mset(s, _) memset(s, _, sizeof(s))
#define pb push_back
#define pii pair <int, int>
#define mp(a, b) make_pair(a, b)
 
inline int read() {
  int x = 0, neg = 1; char op = getchar();
  while (!isdigit(op)) { if (op == '-') neg = -1; op = getchar(); }
  while (isdigit(op)) { x = 10 * x + op - '0'; op = getchar(); }
  return neg * x;
}
inline void print(int x) {
  if (x < 0) { putchar('-'); x = -x; }
  if (x >= 10) print(x / 10);
  putchar(x % 10 + '0');
}
 
int a[15], n;
int main() {
  int T = 10;
  while (T--) {
    n = read();
    for (int i = 0; i < n; i++) a[i] = read();
    int ok = 0;
    int all = 1 << n;
    for (int st = 1; st < all; st++) {
      int Xor = 0;
      for (int i = 0; i < n; i++) if (st >> i & 1) {
        Xor ^= a[i];
      }
      if (!Xor) {
        ok = 1;
        break;
      }
    }
    if (ok) puts("NO");
    else puts("YES");
  }
  return 0;
}

学园祭的游戏

Description

给定\(n\)堆石子,每堆石子有两个值\(num_i\)\(b_i\),表示石子数和一个常数。两个人在博弈。
如果取第\(i\)堆石子,那么至多\(\lfloor \frac{num_i}{b_i} \rfloor\) 个石子,与此同时\(num_i\)也相应减少。
最后不能操作的人输。
多组数据。
数据范围 \(1\le T\le 100, 1\le n\le 20, 1\le a_i, b_i\le 10^9\)

Solution

显然每堆石子独立,所以最后只需要将每堆石子的\(SG\)值异或起来即可。
对于每一堆石子,它内部也是一个状态集合。
\(SG(n)=mex(SG(n-1),SG(n-2),...,SG(n-\lfloor \frac{n}{b} \rfloor ))\)
\(n\in [0, b)\)时,\(SG\)值肯定为\(0\)
否则我们用数学归纳法,可知:
\(\begin{cases}SG(n)=\frac{n}{b}, \ [b | n] \\ SG(n)=SG(n-\lfloor \frac{n}{b} \rfloor -1), \ otherwise \end{cases}\)
然后根号分治优化即可。

Code

// Author: wlzhouzhuan
#pragma GCC optimize(2)
#pragma GCC optimize(3)
#include <bits/stdc++.h>
using namespace std;

#define ll long long
#define ull unsigned long long
#define rint register int
#define rep(i, l, r) for (rint i = l; i <= r; i++)
#define per(i, l, r) for (rint i = l; i >= r; i--)
#define mset(s, _) memset(s, _, sizeof(s))
#define pb push_back
#define pii pair <int, int>
#define mp(a, b) make_pair(a, b)

inline int read() {
  int x = 0, neg = 1; char op = getchar();
  while (!isdigit(op)) { if (op == '-') neg = -1; op = getchar(); }
  while (isdigit(op)) { x = 10 * x + op - '0'; op = getchar(); }
  return neg * x;
}
inline void print(int x) {
  if (x < 0) { putchar('-'); x = -x; }
  if (x >= 10) print(x / 10);
  putchar(x % 10 + '0');
}

int SG(int n, int b) {
  while (n % b) {
    n -= (int)ceil((n % b) / 1.0 / (n / b + 1)) * (n / b + 1);
  }
  return n / b;
}
int n;
int main() {
  int T = read();
  while (T--) {
    n = read();
    int ans = 0;
    while (n--) {
      int n = read(), b = read();
      ans ^= SG(n, b);
    }
    puts(ans ? "Setsuna" : "Kazusa");
  }
  return 0;
}
posted @ 2020-04-09 17:03  wlzhouzhuan  阅读(331)  评论(0编辑  收藏  举报