【Codeforces Round #696 (Div. 2) D】Cleaning
题目链接
翻译
你可以选择相邻的两个都不为空的石头堆,从两个石头堆(每个石头堆中有多个石头)中都拿去一个石头。
你可以进行无数次这样的操作,问你最终能否将所有的石头都拿走。
石头是在一条直线上排列的,从左到右标号为 \(1\) 到 \(n\)。
题解
会发现第一个位置上的石头,只能通过拿堆 \(1\) 和堆 \(2\) 全部拿走。
然后堆 \(1\) 全拿完之后,堆 \(2\) 变成新的堆 \(1\) 了,所以也只能拿头两堆的石头。
以此类推,我们就可以这样每次贪心地拿头两堆完成一次 \(check\),看是否能够在不使用超能力的情况下拿走全部石头了。
如果不能够做到的话,就需要使用超能力了。
这里先提醒一点,我们同样可以从最后一堆开始拿起,因为最后一堆也只能通过每次拿倒数的两堆实现全都拿走。
因此,我们可以设 \(pre_i\) 和 \(suf_i\) 分别表示 \(1..i-1\) , \(i+1..n\) 这些堆的石头全部被拿走了,然后第 \(i\) 堆石头剩余的石头数目。
会发现我们交换相邻的两堆石头 \(i\) 和 \(i+1\), 问题就转化为 \(pre_{i-1}\) 和 \(a[i+1]\) 以及 \(a[i]\) 和 \(suf_{i+2}\) 这四个位置的石头堆能否全都
拿走了,因为 \(1..i-2\) 和 \(i+3..n\) 这些位置上的石头能否拿走都不会受这次 \(swap\) 的影响。
\(\mathcal{O}(N)\) 枚举交换位置即可。
代码
#include <bits/stdc++.h>
#define LL long long
using namespace std;
const int N = 2e5;
int T, n;
int a[N + 10],pre[N+10],suf[N+10];
bool ok(vector<int> v) {
int len = v.size();
for (int i = 0; i <= len - 2; i++) {
if (v[i] < 0) {
return false;
}
v[i + 1] -= v[i];
v[i] = 0;
}
for (int x : v) {
if (x != 0) {
return false;
}
}
return true;
}
int main() {
#ifdef LOCAL_DEFINE
freopen("in.txt", "r", stdin);
#endif // LOCAL_DEFINE
ios::sync_with_stdio(0), cin.tie(0);
cin >> T;
while (T--) {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
pre[1] = a[1];
for (int i = 2; i <= n; i++) {
if (pre[i - 1] < 0) {
pre[i] = -1;
continue;
}
pre[i] = a[i] - pre[i - 1];
}
suf[n] = a[n];
for (int i = n - 1; i >= 1; i--) {
if (suf[i + 1] < 0) {
suf[i] = -1;
continue;
}
suf[i] = a[i] - suf[i + 1];
}
if (pre[n] == 0) {
cout << "YES" << endl;
continue;
}
bool finalOK = false;
for (int i = 1; i <= n - 1; i++) {
//swap(a[i],a[i+1])
vector<int> v(0);
if (i > 1) {
v.push_back(pre[i - 1]);
if (pre[i - 1] < 0) {
continue;
}
}
v.push_back(a[i+1]);v.push_back(a[i]);
if (i <= n - 2) {
if (suf[i + 2] < 0) {
continue;
}
v.push_back(suf[i+2]);
}
if (ok(v)) {
finalOK = true;
break;
}
}
if (finalOK) {
cout << "YES" << endl;
}
else {
cout << "NO" << endl;
}
}
return 0;
}