题解:P13309 演剧
超好玩的题!
前言
场切了。
博弈论的那些专业的东西我也不懂,但是找规律我们小学就会对吧。比赛的时候觉得这题我怎么可能做出来,但是看到后面的题更不会,不死心,还是来硬刚这道。通过不断的排除和尝试,测试近百个小数据之后,终于发现了最重要的一个博弈规律,虽然场上没有严谨地证明(赛后 5 分钟就证出来了),但是直接写发现就 AC 了。
这告诉我们不要害怕难题,再难的想法,再复杂的算法,都是从最简单的东西里来的。这样的思维题若做不出来一定要不断尝试,因为看似捉摸不透的规律在数据之中便可一目了然。
思路
首先容易想到二分答案,为啥呢?例如我们要 check 雪能不能让最终剩下的数
一番操作下来,我们只需要对于只有
我们从最最最简单的情况入手:
如果整个序列全是
那么我们可以说句废话,如果雪要赢,最后肯定是全是
第一种情况
记序列
当然是有的。
雪只需要保证自己每次分割都让序列变成这样两个序列:
- 其中一个序列
c_1=c_0 ,且不存在一个前缀或后缀也满足c_1=c_0 (这里的前后缀不能是自身); - 另一个序列随意,容易发现也满足
c_1>c_0 。
这个分割一定是可以达成的,证明较简单便不赘述。
接下来 K 就过来删了,如果删
所以无论如何,这个序列都会一直满足
综上,若原序列
第二种情况
假设最开始序列满足
因为 K 是后手,所以需要让雪先来分割,但是无论雪如何分割,总有一段会保持
综上,若原序列
第三种情况
剩下的就是
雪先来分割,雪非常想赢,雪直接拿出了自己之前的招式,分割成了这样:
- 其中一个序列
c_1=c_0 ,且不存在一个前缀或后缀也满足c_1=c_0 (这里的前后缀不能是自身); -
另一个序列随意,容易发现也满足
c_1=c_0 。K 如果选了前者来分割,那他怎么分割都会有一个
c_1>c_0 的数列,被雪选走就转化成了之前的情况,必输,所以 K 只能选后者。雪发现这样似乎一眼看不到胜利的感觉,所以想着换种分割方法是不是更好,但是仔细一想,如果分割出来一个
c_1>c_0 的序列,一个c_1<c_0 的序列后者被选走就更废了,只能分成两个c_1=c_0 的序列稳妥。对于 K 也是同理,K 拿到序列后也必须这样分。
那最后谁会赢呢?如果对方拿到一个不存在前缀或后缀也满足
c_1=c_0 的序列自己就赢了。为了方便考虑,我们可以先统计出原序列是由多少个存在前缀或后缀也满足
c_1=c_0 的子串组成的。因为雪和 K 的分割都不会擅自在某个这样的子串中间割开,否则自己就输了。
设这个数为
如果一方需要对
如果你对奇偶性敏感,很快就会发现一条规律,一方拿到任何奇数
因为分割的时候奇数只能分成 奇数 + 偶数,对方选走偶数,然后分成 奇数 + 奇数,最后自己还是拿到一个奇数。如此往复下去,因为对方永远不会拿到奇数,而终将有人拿到
综上,若
总结
以上三种情况的判断即为 check 函数的全部任务,都可以做到线性,写起来也不复杂,最重要的还是构造博弈方法。
参考代码
时间复杂度
#include<bits/stdc++.h>
using namespace std;
int T, n, a[100010], p[100010], q[100010], cnt[2];
bool c[100010];
bool chk(int x) {
cnt[1] = cnt[0] = 0;
for (int i = 1; i <= n; i++)
cnt[c[i] = a[i] >= x]++;
if (cnt[1] > cnt[0]) return 1;
else if (cnt[1] < cnt[0]) return 0;
int l1 = 0, l2 = 0, res = 0;
for (int i = 1; i <= n; i++) {
if (c[i]) l1++;
else l2++;
if (l1 == l2) res++;
}
if (res % 2 == 0) return 1;
else return 0;
}
int main() {
scanf("%d", &T);
while (T--) {
scanf("%d", &n);
for (int i = 1; i <= n; i++)
scanf("%d", &a[i]);
int l = 1, r = 1e9;
while (l < r) {
int mid = l + r + 1 >> 1;
if (chk(mid)) l = mid;
else r = mid - 1;
}
cout << l << endl;
}
return 0;
}
后记
本篇题解到这里就结束了,希望大家都能在尝试与探索中找到乐趣,享受通过一次次努力得到结论的喜悦!