agc026_f
题目叙述
两个人做游戏,一共有 (n) 堆石子排成一行,需要遵循以下规则:
- 如果这是第一轮,或者上一轮取的那堆石子相邻的堆中没有一个是有石子的,那么可以随便选一堆没有被取的,将它全取光。
- 否则必须在相邻的堆中选择一堆没有被取过的石子取了。
两个人都希望自己得到的石子数量尽量多,并且两个人都绝顶聪明,问最终第一个人会得到多少个石子,第二个人会得到多少个石子。
题解
可以观察到一个非常显然的事实,只要前两步定了那么就会跟着一大堆步骤都定了(即先手定位置,后手定往左取还是往右取),一直取到左边界或者右边界,这样的话剩下的棋子堆就是一个区间。所以可以设 (f_{l,r}) 表示区间 ([l,r]) 内的先手能得到多少金币,后手可以通过区间和 - (f_{l,r}) 就行了。然而这样复杂度是 (mathcal O(n^3)) 的,很慢。
事实上上面的做法与正解并不沾边。考虑对全序列长度的奇偶性分类。下面称位置为从左往右数奇数个的位置为奇数位,从左往右数偶数个的位置为偶数位。
首先考虑 (n) 是偶数的情况。设偶数位石子数之和为 (x) ,奇数位之和为 (y) 。那么先手必然可以取到 (max{x,y}) 这么多石子,即最优策略下得到的石子数量至少为 (max{x,y})。再考虑后手,无论先手取哪里后手都可以掌控先手取的位置左边的部分与右边部分之一的取的位置,即至少有一边可以后手转化为先手。所以后手必然可以保证可以隔一个取一个,那么最优策略下得到的石子数量至少为 (min{x,y}) 。而 (max{x,y}+min{x,y}=x+y) ,所以先手最优策略只能得到 (max{x,y}) 个石子,后手最优策略能得到 (min{x,y}) 个石子。
其次考虑 (n) 是奇数的情况。考虑每次如果先手选择一个奇数位,那么不论走哪一边都将失去下一次的选择权。如果选择一个偶数位,那么不论走哪一边下一次都仍然拥有选择权。
先考虑失去选择权后先手的最优策略。先手第一步满足操作后会失去下一波的选择位置的权力,所以必然选择了一个奇数位,那么先手至少得到所有奇数位的石子数之和,后手在先手选择了奇数位后将得到下一轮的控制权,所以至少能得到所有偶数位的石子数之和。所以最终先手最优策略必然得到所有奇数位的石子数之和,后手最优策略必然得到所有偶数位的石子数之和。
接下来考虑先手在丢掉选择权之前在做什么。每次选择一个位置,然后后手选择保留右边还是左边。如果先手每次选的位置都确定了,那么后手就相当于选择这些位置中相邻的位置组成一个区间,作为先手丢掉选择权的位置。所以先手实际上需要做的选择就是选择哪些位置当作可能丢掉选择权的区间的端点。考虑选手出去丢掉选择权的区间,剩下的区间得到的石子数就是所有偶数位的石子数之和。那么先手要做的就是最小化所有区间的 偶数位石子数 - 奇数位石子数 中的最大值。那么二分+贪心就可以解决了。
这题的最大值最小值分析很玄妙啊。
代码
#include <cstdio>
#include <iostream>
using namespace std;
const int NN = 3e5 + 5;
int N, a[NN];
bool check(int X) {
int t = 0;
for (int i = 1; i <= N; ++i) {
if (i & 1) t += a[i];
else if (t >= X) t = max(t - a[i], 0);
else t -= a[i];
}
return t >= X;
}
int main() {
scanf("%d", &N);
for (int i = 1; i <= N; ++i)
scanf("%d", &a[i]);
int B = 0, W = 0;
for (int i = 1; i <= N; ++i) {
if (i & 1)
B += a[i];
else
W += a[i];
}
if (!(N & 1)) {
printf("%d %d
", max(B, W), min(B, W));
return 0;
}
int L = 0, R = B, ans = 0;
while (L <= R) {
int mid = (L + R) >> 1;
if (check(mid)) {
ans = mid;
L = mid + 1;
} else
R = mid - 1;
}
printf("%d %d
", W + ans, B - ans);
return 0;
}