AT_arc138_c [ARC138C] Rotate and Play Game 题解
AT_arc138_c [ARC138C] Rotate and Play Game 题解
蒟蒻太菜了,打模拟赛被此题爆杀,发现此题从思考方式和题目转化上都有较大启发,故写一篇题解纪念。^^
I. 题意
有 \(n\) 张卡牌编号为 \(1\sim n\),第 \(i\) 张卡片上的数为 \(a_i\)。先手 Snuke 可以拿走任意一张牌,后手 Min 只能拿走最左边的牌。Snuke 的得分为他拿走的牌的分数总和,目标要使 Snuke 的得分最大。
有一种操作,选择一个数 \(k(0\le k\le n - 1)\),将卡牌数列左移 \(k\) 位,即讲卡牌顺序变为 \(a_{k + 1}, a_{k + 2}, \dots, a_n, a_1, a_2, \dots, a_k\)。
求问符合条件的任意 \(k\) 和 Snuke 的最大得分。
II.思路
首先,我们有如下几个显而易见的条件:
- 游戏会进行 \(\frac{n}{2}\) 轮,Snuke 只能拿走 \(\frac{n}{2}\) 张卡牌;
- Snuke 如何操作,\(k\) 如何变幻,Snuke 的分数理论最大为 \(n\) 张卡牌中分数前 \(\frac{n}{2}\) 大的卡牌。
- Min 只会取最左边的牌,不会干扰我们取牌。也就是说,\(k\) 和 Snuke 拿牌顺序一定,那么 Min 会取哪些拍我们也知道。
那么我们尝试想:我猜出题人肯定想让我拿满这个上界。如果我能构造出一种情况让 Snuke 把所有大数全拿走,那就直接赢了,不用考虑复杂的博弈。
于是,Snuke 的目标变为拿走分数前 \(\frac{n}{2}\) 的卡牌,所以分数确定。故而这 \(n\) 个数在我们眼中,只有分数是否为前 \(\frac{n}{2}\) 大是被我们所关注的。所以,我们将分数前 \(\frac{n}{2}\) 的卡牌标记为 \(1\),否则标记为 \(0\)。
其次,要想分数为理论最大值,Snuke 就要拿走所有的 \(1\),而 Min 只能拿 \(0\)。
直接考虑方案感觉较难,考虑正难则反,看怎么拿牌会破坏让 Min 只拿 \(0\) 的约定。观察第 \(i\) 轮 Min 拿牌后:
- 此时 Snuke 和 Min 各拿走 \(i\) 张牌,总共 \(2\times i\) 张牌被拿走。
- 若这 \(2\times i\) 张牌中,\(1\) 的数量大于 \(i\) 张,则即使 Snuke 拿走 \(i\) 张 \(1\),Min 也会拿走至少 \(1\) 张 \(1\),不满足条件。
综上,若我们要 Min 拿不到任何 \(1\),前 \(2\times i\) 个位置里,要保持 \(1\) 的数量不大于 \(0\) 的数量。
这不是 Catalan 数板子题吗?不过这不是计数题,我们考虑参考 Catalan 数的解法:
考虑将问题转化成:你初始时站在已平面直角坐标系中,位于点 \((0, 0)\),牌堆为操作序列, \(1\) 是向上走 \(1\) 单位,\(0\) 是向下走 \(1\) 单位。
以样例 5 9 7 4 2 8 2 3 为例,则序列转换为 1 1 1 0 0 1 0 0,如下图:

显然这个样例最有情况为 \(k = 3\) 时得分为最大值 \(29\)。我们猜测这个 \(k\) 可能与折现最高点有关。
更严谨地,设原标记序列为 \(b_1,b_2,\dots,b_n\),其中大牌记作 \(+1\),小牌记作 \(-1\)(这里把前面的 \(0\) 换成 \(-1\) 只是为了求和方便,不影响本质)。定义前缀和
因为大牌和小牌各占一半,所以 \(S_n=0\)。
现在考虑循环左移 \(k\) 位后的序列,它的第 \(i\) 个前缀和为
由于 \(S_n=0\),上面两式可以统一写成 \(S'_i = S_{(k+i)\bmod n} - S_k\),其中 \(S_n=S_0=0\)。
我们要让 Min 始终拿不到大牌,等价于要求任意前缀中大牌数不超过小牌数,即 \(\forall i=1,2,\dots,n\),有 \(S'_i \le 0\)。
而 \(S_{(k+i)\bmod n}\) 遍历了 \(S_0,S_1,\dots,S_{n-1}\)(当 \(i\in [1, n]\) 时,\((k+i)\bmod n\) 正好遍历所有剩余下标)。因此,要使所有 \(S'_i\le 0\),只需 \(\forall j=0,1,\dots,n-1\) 有 \(S_k \ge S_j\)。
即 \(S_k\) 是这些前缀和中的最大值。
所以,最优的 \(k\) 就是前缀和序列 \(S_0,S_1,\dots,S_{n-1}\) 中最大值第一次出现的位置(下标从 \(0\) 开始)。若最大值在位置 \(p\),则令 \(k=p\)(若 \(p=0\) 则输出 \(0\),否则输出 \(p\),因为循环左移 \(p\) 位恰好对应)。这就是为什么我们在代码中要找前缀和的最大值,最后输出时还要把下标减 \(1\)(因为代码里下标从 \(1\) 开始,而最大值位置 \(mxp\) 是 \(1\)-based,实际 \(k=mxp-1\))。
III.代码
#include<bits/stdc++.h>
#define endl '\n'
#define pi pair<int, int>
#define int long long
using namespace std;
const int N = 2e5 + 10;
int n, b[N];
struct node{
int x, id;
bool operator<(const node &other) const{
return x < other.x;
}
} a[N];
signed main(){
freopen("rotate.in", "r", stdin);
freopen("rotate.out", "w", stdout);
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i].x;
// a[i].y = a[i].x;
a[i].id = i;
}
sort(a + 1, a + 1 + n);
int ans = 0;
for (int i = n / 2 + 1; i <= n; i++) ans += a[i].x;
for (int i = 1; i <= n / 2; i++) b[a[i].id] = -1;
for (int i = n / 2 + 1; i <= n; i++) b[a[i].id] = 1;
int k = -1, ma = -1, h = 0;
for (int i = 1; i <= n; i++) {
h += b[i];
if (h >= k) ma = h, k = i - 1;
}
cout << k << " " << ans << endl;
return 0;
}
IV. 坑点
对于大部分样例,以 5 9 7 4 2 8 2 3 为例,\(k = 2\) 和 \(k = 3\) 时都是可行的,此时序列分别为:
- \(k = 2\) 时,序列为
7 4 2 8 2 3 5 9。先取 \(7, 8\),之后任意顺序取 \(5, 9\),得分为 \(29\)。 - \(k = 3\) 时,序列为
4 2 8 2 3 5 9 7。先取 \(8\),之后任意顺序取 \(8, 5, 9, 7\),得分为 \(29\)。
可见对于大部分情况,\(k\) 取最高点和最高点前一个点都是合法的,因为你可以开局拿走第一个 \(1\),而因为这个时最高点,所以下一个肯定是 \(0\),所以不影响我们的得分。
但是,对于最高点在 \(0\) 的样例,直接输出 \(k\) 而不减 \(1\) 会导致错误。
如:
4
2 3 1 4
应输出 0 7 或 1 7 或 2 7 或 3 7。
而根据题意,有 \(0\le k\le N - 1\),如果 \(k\) 不减 \(1\),输出 4 7 是错误的。

浙公网安备 33010602011771号