P11133【MX-X5-T5】「GFOI Round 1」World Ender 题解
chenly8128 · · 题解
前置知识
- 博弈论
- 状态转移
找规律
找规律
根据标准的博弈论思想,我们可以记录每一个状态下,先手有没有必胜策略。如果能转移到一个先手必败的状态,那么有先手必胜策略;反之亦然。
可以发现每一次操作碎片至少减少一块,所以不可能出现循环,状态转移是比较明确的。
然后开始手推不同的状态。我们用
n = 1 时:
很显然,先手必胜,全部丢弃即可。
n = 2 时:
根据博弈论的基本原理,可以得到如果先手率先把其中的一堆丢光,那么只剩下一堆了,会导致后手获胜,先手必败。
当两堆个数分别为
证明:运用归纳法。
证明完毕。
n = 3 时:
结论:先手必胜。
因为,可以把碎片数量最大的一堆给选出来,用它的碎片把数量最小的一堆给补到与另外一堆数量相等,然后把剩下的碎片全部丢弃。就转移到了两堆的先手必败情况,所以
n 为偶数时:
可以通过总结规律得到判断方法:
- 设每一堆碎片的数量组成长度为
n 的数组a_{1,2,\dots,n} 。 - 将
a 从小到大排序。 - 如果存在
i 使得a_{2i-1} < a_{2i} ,那么先手必胜。反之先手必败。
证明类似于
n 为奇数时:
与
证明类似。
实现
当你发现了上述规律,并且能够说明为什么了的时候,你就差不多解决了这道题了。
Init
根据上述规律排序并判断即可。
Get
记录上一步的数组,我的实现中 GET 函数几乎什么都没做。
Play
这是重点!!!分成两种情况:
这样就转移成了必败状态。
代码
#include <cstdio>
#include <vector>
#include <algorithm>
#include <cassert>
using namespace std;
struct point {
int id,val;
bool operator < (const point b) const {
return val < b.val;
}
};
vector <point> v;
vector <int> Play(void) {
int n = v.size();
sort (v.begin(),v.end());
if (n&1) {
vector <int> res(n);
for (int i = 0;i < n-1;i += 2) {
res[v[i].id] = v[i+1].val;
res[v[i+1].id] = v[i+1].val;
}
return res;
}
vector <int> res(n);
vector <point> v2(0);
for (int i = 0;i < n;i++) {
if (i < n-1 && v[i].val == v[i+1].val) {
res[v[i].id] = v[i].val;
res[v[i+1].id] = v[i+1].val;
i++;
continue;
}
else v2.push_back(v[i]);
}
if (v2.size()) {
assert (v2.size()>1);
for (int i = 1;i < v2.size()-1;i += 2)
res[v2[i].id] = res[v2[i+1].id] = v2[i+1].val;
res[v2[0].id] = v2[0].val;
res[v2[v2.size()-1].id] = v2[0].val;
}
fflush(stdout);
return res;
}
void Get(vector<int> a) {
v.clear();
for (int i = 0;i < a.size();i++)
v.push_back((point){i,a[i]});
return;
}
bool Init(int n, int op,vector<int> a) {
v.clear();
for (int i = 0;i < a.size();i++)
v.push_back((point){i,a[i]});
if (a.size()&1) return false;
sort (a.begin(),a.end());
for (int j = 0;j < a.size()-1;j += 2)
if (a[j] != a[j+1]) return false;
return true;
}
注意不要用 C++14(GCC 9)。轻松 AC。