Codeforces 851E

题目

给$N$个数${a_i}$,Alice和Bob轮流选进行最优操作。
一次操作定义为:取一个数$p^k(p为质数,k>0)$,这个数要求能被某个$a_i$整除。然后将这些数中整除$p^k$的数都除以$p^k$。
若不能选出一个这样的数$p^k$,则输。
Alice先手,问谁嬴。

数据范围

$1\le N \le 100 \quad 1\le a_i \le 10^9$

做法

发现:对不同质数而言,游戏是相互独立的。并且,对于不同质数,游戏满足SG组合游戏的性质。所以,整个游戏就是若干个SG组合游戏的游戏和,可以转为NIM游戏来做。
然后问题就变成了:求出每种质数$p$单独存在时的游戏的SG值。可以通过状态压缩来表示当前游戏的状态,以方便在状态间相互转移。具体实现为:用一个整数$mask$来压缩状态。若一个数$a$能被$p^k$整除但不能被$p^{k+1}$整除的时候,则置$mask$的第$k-1$位为1,即mask |= 1 << (k - 1)。对所有整除$p^k$的数都除以$p^k$可以实现为:newMask = (mask >> k) | (mask & ((1 << (k - 1)) - 1))

代码

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
58
59
60
#include <bits/stdc++.h>
using namespace std;
const int MAX_N = 105;
int N, a[MAX_N];
vector<int> primes;
map<int, int> sg;
void dfs(int mask)
{
if (sg.find(mask) != sg.end()) return;
vector<int> mex(33, 0);
for (int i = 1; mask >> (i - 1); ++i) {
int newMask = (mask >> i) | (mask & ((1 << (i - 1)) - 1));
dfs(newMask);
mex[sg[newMask]] = 1;
}
int ret = 0;
while (mex[ret] != 0) ++ret;
sg[mask] = ret;
}
int main()
{
cin >> N;
for (int i = 0; i < N; ++i) {
scanf("%d", &a[i]);
int t = a[i];
for (int j = 2; j * j <= t; ++j) {
int cnt = 0;
while (t % j == 0) {
t /= j;
++cnt;
}
if (cnt) primes.push_back(j);
}
if (t != 1) primes.push_back(t);
}
sort(primes.begin(), primes.end());
primes.erase(unique(primes.begin(), primes.end()), primes.end());
sg[0] = 0;
int ans = 0;
for (int p : primes) {
int mask = 0;
for (int i = 0; i < N; ++i) {
int cnt = 0;
while (a[i] % p == 0) {
a[i] /= p;
++cnt;
}
if (cnt) mask |= 1 << (cnt - 1);
}
dfs(mask);
ans ^= sg[mask];
}
puts(ans ? "Mojtaba" : "Arpa");
return 0;
}

细节

在暴力求SG函数的时候,由于一个状态最多有30个后继,所以可以用反证法证明:每个状态的SG值不会超过30,于是dfs时的保存后继中出现过哪些SG值的数组开31就够了。