1 条题解
-
0
思路与算法
每组读入一个合法取数集合S,独立计算所有0至10000颗石子的SG。初始g[0]=0,递推g[x]=mex({g[x-s] | s在S且s<=x})。先排序取数,遇到比x大的数即可停止;时间戳数组记录至多k种后继SG。
接着读取该组m个局面。每个局面有n堆,把各堆g[a_i]异或;非零输出W,零输出L。每局面只输出一个字符,每组m个字符之后换行。继续读取下一组,k=0或文件结束时停止,取数和SG数组每组重新创建。
正确性说明
每个取数均为正数,所以一堆的合法转移严格减少大小。g[0]=0符合无操作终态;按x递增,所有后继已经求出,mex递推得到正确的单堆SG。
一次操作只改变一堆,且各堆取数规则互不干扰,整个局面的SG是各堆SG异或。游戏和定理给出异或为0恰为先手必败,其余为先手必胜,因此W/L判断正确。每组只使用该组取数集合计算的SG,m个局面按输入顺序逐个输出,所以组间不会混用状态,输出字符串也与局面一一对应。
复杂度
令V=10000。单组排序O(k log k),SG数组初始化O(V),标记初始化O(k);每个x最多检查k个后继且mex<=k,预处理O(Vk)。若该组共有H堆(所有局面的n之和),读取和异或O(H),输出O(m)。单组总时间O(k log k+V+Vk+H+m),额外空间O(V+k),不缓存所有局面。多组时间对各组求和,空间只保留当前组。
易错点
- 同一组输出m个字符,题面输出段的n指代笔误应以输入的m个局面及样例为准;不是每堆一个字符。
- 每组换行,字符之间不插空格。
- k=0是终止标记,不读取后续集合或局面。
- 下一组可能有不同取数集合,必须重置SG和标记;集合输入不保证已排序。
- 允许取数比当前堆大,此时跳过;测试覆盖最大石子数和100种取数,不能假定集合包含1。
参考代码(C++11)
#include <algorithm> #include <iostream> #include <vector> using namespace std; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int k; while (cin >> k && k) { vector<int> s(k); for (int &x : s) cin >> x; sort(s.begin(), s.end()); vector<int> sg(10001), seen(k + 1, -1); for (int x = 1; x <= 10000; ++x) { for (int t : s) { if (t > x) break; seen[sg[x - t]] = x; } while (seen[sg[x]] == x) ++sg[x]; } int m; cin >> m; while (m--) { int n, sum = 0; cin >> n; while (n--) { int x; cin >> x; sum ^= sg[x]; } cout << (sum ? 'W' : 'L'); } cout << '\n'; } }
- 1