CSP-S 模拟赛34
T1
考虑对原序列将 \(k\) 的左右分成两个序列。simple 的想法是分别从 \(1\) 开始跑前缀和,每一次总跑到下一个小于它的点,然后依次类推。发现这样做碰到序列最小值之后难以继续。
然而我们发现这样跑点的过程从前往后和从后往前是等价的。这样考虑的原因是发现这样的选数问题不具有方向性。于是时间复杂度 \(O(n)\)。
代码:
#include <bits/stdc++.h>
#define N 100005
#define int long long
using namespace std;
int T;
int n, k;
int a[N];
int a1[N], a2[N];
int ct1, ct2;
int sm1[N], sm2[N];
int nx1[N], nx2[N];
void sve() {
memset(sm1, 0, sizeof sm1);
memset(sm2, 0, sizeof sm2);
memset(nx1, 0, sizeof nx1);
memset(nx2, 0, sizeof nx2);
cin >> n >> k;
for (int i = 1; i <= n; i++)
scanf("%lld", &a[i]);
ct1 = ct2 = 1;
a1[1] = a2[1] = 0;
for (int i = k; i > 1; i--)
a1[++ct1] = a[i];
for (int i = k + 1; i <= n; i++)
a2[++ct2] = a[i];
for (int i = 1; i <= ct1; i++)
sm1[i] = sm1[i - 1] + a1[i];
for (int i = 1; i <= ct2; i++)
sm2[i] = sm2[i - 1] + a2[i];
int mn = sm1[1], nw = 1;
for (int i = 2; i <= ct1; i++)
if (sm1[i] <= mn) {
nx1[nw] = i;
nw = i;
mn = sm1[i];
}
mn = sm1[ct1], nw = ct1;
for (int i = ct1 - 1; i >= 1; i--)
if (sm1[i] < mn) {
nx1[nw] = i;
nw = i;
mn = sm1[i];
}
mn = sm2[1], nw = 1;
for (int i = 2; i <= ct2; i++)
if (sm2[i] <= mn) {
nx2[nw] = i;
nw = i;
mn = sm2[i];
}
mn = sm2[ct2], nw = ct2;
for (int i = ct2 - 1; i >= 1; i--)
if (sm2[i] < mn) {
nx2[nw] = i;
nw = i;
mn = sm2[i];
}
if (sm1[ct1] + sm2[ct2] > 0)
return puts("No"), void();
int p1 = 1, p2 = 1;
while (nx1[p1] || nx2[p2]) {
if (!nx1[p1]) {
for (int j = p2 + 1; j <= nx2[p2]; j++)
if (sm1[p1] + sm2[j] > 0) {
puts("No");
return;
}
p2 = nx2[p2];
continue;
}
if (!nx2[p2]) {
for (int j = p1 + 1; j <= nx1[p1]; j++)
if (sm1[j] + sm2[p2] > 0) {
puts("No");
return;
}
p1 = nx2[p1];
continue;
}
int fg = 0;
for (int i = p1 + 1; i <= nx1[p1]; i++)
if (sm1[i] + sm2[p2] > 0) {
fg = 1;
break;
}
if (!fg) {
p1 = nx1[p1];
continue;
}
for (int j = p2 + 1; j <= nx2[p2]; j++)
if (sm1[p1] + sm2[j] > 0) {
puts("No");
return;
}
p2 = nx2[p2];
}
p1 = ct1, p2 = ct2;
while (nx1[p1] || nx2[p2]) {
if (!nx1[p1]) {
for (int j = p2 - 1; j >= nx2[p2]; j--)
if (sm1[p1] + sm2[j] > 0) {
puts("No");
return;
}
p2 = nx2[p2];
continue;
}
if (!nx2[p2]) {
for (int j = p1 - 1; j >= nx1[p1]; j--)
if (sm1[j] + sm2[p2] > 0) {
puts("No");
return;
}
p1 = nx2[p1];
continue;
}
int fg = 0;
for (int i = p1 - 1; i >= nx1[p1]; i--)
if (sm1[i] + sm2[p2] > 0) {
fg = 1;
break;
}
if (!fg) {
p1 = nx1[p1];
continue;
}
for (int j = p2 - 1; j >= nx2[p2]; j--)
if (sm1[p1] + sm2[j] > 0) {
puts("No");
return;
}
p2 = nx2[p2];
}
puts("Yes");
}
signed main() {
freopen("game.in", "r", stdin);
freopen("game.out", "w", stdout);
cin >> T;
for (int i = 1; i <= T; i++)
sve();
return 0;
}
T2
显然考虑 \(O(n^2)\) 的 dp。
朴素的 dp 定义是 \(dp_{i,j}\) 表示长度为 \(i\) 的序列,\(j\) 次消除的方案数。然而发现这样转移的复杂度难以接受,需要分别枚举左右区间的消除次数。
考虑某一个位置 \(x\) 的消除次数由什么决定。对于只有某一边有 \(>a_x\) 的,则这个位置的消除次数一定是 \(j-1\)。对于两边都有 \(>a_x\) 的,两边的消除次数有一个是 \(j-1\)。那么就考虑前缀和优化这个 dp,则定义 \(dp_{i,j,0/1}\) 表示长度为 \(i\) 的序列,至多 \(j\) 次消除,有一边 / 两边 \(>a_x\) 的方案数,那么 \(j\) 便由 \(j,j-1\) 转移而来。
时间复杂度大抵是 \(O(n^2\log^2n)\)。
标签:p2,p1,nx1,nx2,int,sm2,34,CSP,模拟 From: https://www.cnblogs.com/Rock-N-Roll/p/18447336