AT_arc138_c [ARC138C] Rotate and Play Game 题解

AT_arc138_c [ARC138C] Rotate and Play Game 题解

ATcoder 原题链接
洛谷链接

蒟蒻太菜了,打模拟赛被此题爆杀,发现此题从思考方式和题目转化上都有较大启发,故写一篇题解纪念。^^

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.思路

首先,我们有如下几个显而易见的条件:

  1. 游戏会进行 \(\frac{n}{2}\) 轮,Snuke 只能拿走 \(\frac{n}{2}\) 张卡牌;
  2. Snuke 如何操作,\(k\) 如何变幻,Snuke 的分数理论最大为 \(n\) 张卡牌中分数前 \(\frac{n}{2}\) 大的卡牌。
  3. 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_0=0,\quad S_i=\sum_{j=1}^{i} b_j \quad (1\le i\le n) \]

因为大牌和小牌各占一半,所以 \(S_n=0\)

现在考虑循环左移 \(k\) 位后的序列,它的第 \(i\) 个前缀和为

\[S'_i = \begin{cases}S_{k+i}-S_k, & k+i \le n,\\S_{k+i-n}+S_n-S_k = S_{k+i-n}-S_k, & k+i>n.\end{cases} \]

由于 \(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 71 72 73 7

而根据题意,有 \(0\le k\le N - 1\),如果 \(k\) 不减 \(1\)输出 4 7 是错误的。

posted @ 2026-08-17 21:06  酱云兔  阅读(12)  评论(0)    收藏  举报