题解:P16666 [CSPro 25] 博弈论与石子合并
题意
有小 c 与小 z 两位玩家玩一个游戏,轮流操作一个序列
题解
首先观察特殊性质,发现与奇偶性和先后手有关,于是分类讨论。具体的,我们经过简单的手模,发现最后一个操作的人决定了这个游戏的性质,于是分开处理。
小 z 最后操作
这个比较简单。我们观察到小 z 是不希望自己的合并过的东西被删除的,而且似乎他的确有方法避免这一点:即在中间进行合并。
形式化的,我们令
证明从两个方向进行:首先是小 c 可以用某些操作取到
然后是小 z 也可以取得
这样双方会达成一种默契,使得小 z 在中间合并,小 c 在两边删数,直到达成
小 c 最后操作
这时小 c 几乎一定会删到小 z 合并过的数,因此上面的推理不适用了。
我们在反复手模后提出如下猜想:如果可以把区间划分为若干段,使得小 z 足以合并出来且小 c 无法全破坏完,那么答案一定不小于那些段的和的最小值。
这看上去很有前途(虽然我找不出更合理的引入了),所以我们仔细算一下,发现只要有
那么现在唯一的问题就是证明了。这也是本题解的主要内容:
形式化的,我们定义
引理
引理 1(单调性):对
证明:任一
引理 2(丢弃端点):若
证明:由对称性,只需考虑左端点。若左端点在
引理 3(合并段内元素):若
证明:合并只改变该段内部结构,段数不变。
引理 4(贪心划分):
证明:(太典了,这也要证吗?)令贪心得到的段的第
-
- 此时有
s_k \le r_k ,如果r_{k + 1} 存在,那么因为A 中均为正整数,从s_k + 1 到r_{k + 1} 的和必然不小于x ,所以s_{k + 1} 不晚于r_{k + 1} 取得,有s_{k + 1} \le r_{k + 1} 。
归纳证明
对
设结论对
小 c 回合
这时有
因为小
那么只需证:
充分性(
- 合并
A_i, A_{i + 1} :在A 的划分中,这两元素或同段(段数仍\ge L ),或异段(段数变为L - 1 )。均有\ge L - 1 。 - 丢弃端点:由引理 2,
G(A', x) \ge L - 1 。
必要性(
若
设贪心切分为
简单讨论,我们发现要么存在长为
情形 1:某
-
- 否则我们可以把
S_i 和其前面一个元素合并。根据引理 4,我们考虑贪心合并的流程,发现S_i 及其之前的段数减1 ,而之后的部分不受影响。所以G(A', x) \le L - 2 。
情形 2:所有
我们可以丢弃右端。因为
综上,必要性成立。
小 z 回合
这时有
因为小 z 希望值最大,所以我们有:
那么只需证:
充分性(
因为
必要性(
由引理 1 直接得到。
综上,我们完成了归纳证明。命题得证。
那么可以二分答案
CODE
#include <bits/stdc++.h>
const int N = 1e5 + 5;
int n, K, a[N];
int solve0() {
int len = n / 2 + 1, sum = 0, ans = 1e9;
for (int i = 1; i < len; ++i) sum += a[i];
for (int i = len; i <= n; ++i) {
sum += a[i] - a[i - len];
ans = std::min(ans, sum);
}
return ans;
}
bool check(int x) {
int sum = 0, cnt = 0;
for (int i = 1; i <= n; ++i) {
sum += a[i];
if (sum >= x) { ++cnt; sum = 0; }
if (cnt >= n / 2 + 1) return true;
}
return false;
}
int solve1() {
int l = 1, r = 1e9, ans = 0, mid;
while (l <= r) {
mid = l + (r - l) / 2;
if (check(mid)) { ans = mid; l = mid + 1; }
else r = mid - 1;
}
return ans;
}
int main() {
#ifdef PANGUN
freopen(".in", "r", stdin);
freopen(".out", "w", stdout);
#endif
scanf("%d%d", &n, &K);
for (int i = 1; i <= n; ++i) scanf("%d", &a[i]);
if (n % 2 != K) printf("%d\n", solve0());
else printf("%d\n", solve1());
return 0;
}
该说不说,这题出的还是不错的,如果它没出在模拟赛。