比赛链接:https://codeforces.com/contest/1427
A. Avoiding Zero
题意
将 \(n\) 个数重新排列使得不存在为 \(0\) 的前缀和。
题解
计算正、负前缀和,如果二者和为 \(0\),则不存在满足题意的排列,否则将绝对值较大的一方排在前面即可。
代码
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t;
cin >> t;
while (t--) {
int n;
cin >> n;
vector<int> a(n);
int pos = 0, neg = 0;
for (auto &x : a) {
cin >> x;
(x < 0 ? neg : pos) += x;
}
if (pos + neg == 0) {
cout << "NO" << "\n";
continue;
}
if (pos + neg > 0) {
sort(a.begin(), a.end(), greater<>());
} else {
sort(a.begin(), a.end(), less<>());
}
cout << "YES" << "\n";
for (int i = 0; i < n; i++) {
cout << a[i] << " \n"[i == n - 1];
}
}
return 0;
}
B. Chess Cheater
题意
给出一个长为 \(n\) 由 W, L 组成的字符串,如果一个 W 左侧为 W,则它提供 2 分,否则为 1 分。最多可以将 \(k\) 个 L 变为 W,问字符串可以得到的最大分值。
题解
本题的关键是字符串中 W 的有无及两两构成的封闭区间长度。
- 如果全为 L,则字符串的最大分值为 \(max(2k-1,\ 0)\) 。
- 如果存在 W,则每次操作都会至少增加 2 分,如果操作的为两个 W 区间内的最后一个 L,则会额外再增加 1 分。
所以计算字符串的初始分值,加上 \(2 \times min(k,\ cntL)\) 分,然后区间长度排序,每当可以减去一个完整区间长就再加上 1 分。
代码
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t;
cin >> t;
while (t--) {
int n, k;
cin >> n >> k;
string s;
cin >> s;
int cntL = count(s.begin(), s.end(), 'L');
if (cntL == n) {
cout << max(2 * k - 1, 0) << "\n";
continue;
}
vector<int> posW;
for (int i = 0; i < n; i++)
if (s[i] == 'W') posW.push_back(i);
vector<int> seg;
for (int i = 1; i < int(posW.size()); i++)
if (posW[i] - posW[i - 1] - 1 > 0) seg.push_back(posW[i] - posW[i - 1] - 1);
int ans = s[0] == 'W';
for (int i = 1; i < n; i++)
if (s[i] == 'W') ans += s[i - 1] == 'W' ? 2 : 1;
ans += 2 * min(k, cntL);
sort(seg.begin(), seg.end());
for (auto len : seg) if (k >= len) k -= len, ans += 1;
cout << ans << "\n";
}
return 0;
}
C. The Hard Work of Paparazzi
题意
\(r\) 行与 \(r\) 列相交形成了 \(r \times r\) 个点,初始时刻记者位于左下角的 \((1,1)\) 处,接下来给出 \(n\) 个名人的出现时间和位置,出现时间严格递增,问记者最多可以拍到多少名人的照片。
题解
This is a classical dynamic-programming task with a twist.
这是一个有些变化的经典动态规划问题。
与最长上升子序列问题的不同之处的是,本题判断条件由 \(a_j > a_i\) 变为了 \(dis_{ij} \le t_j - t_i\) 以及利用 \(r\) 将 \(O_{(n^2)}\) 优化至了 \(O_{(nr)}\) 。
代码
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int r, n;
cin >> r >> n;
//如果 r == 1,此时只有 1 个点
if (r == 1) {
cout << n << "\n";
return 0;
}
vector<int> t(n + 1), x(n + 1), y(n + 1);
t[0] = 0, x[0] = 1, y[0] = 1;
for (int i = 1; i <= n; i++)
cin >> t[i] >> x[i] >> y[i];
vector<int> dp(n + 1, -1e9), mx_dp(n + 1);
dp[0] = 0; //初始时只有时刻 0 的 (1,1) 可达
for (int i = 1; i <= n; i++) {
//继承之前可达的 dp 状态
for (int j = max(i - 2 * (r - 1), 0); j < i; j++) {
if (abs(x[i] - x[j]) + abs(y[i] - y[j]) <= t[i] - t[j])
dp[i] = max(dp[i], dp[j] + 1);
}
//如果 i 大于等于最长路径,那么对于 dp[0] ~ dp[i - 2 * (r - 1)] 一定是可达的
if (i >= 2 * (r - 1)) dp[i] = max(dp[i], mx_dp[i - 2 * (r - 1)] + 1);
mx_dp[i] = max(dp[i], mx_dp[i - 1]);
}
cout << mx_dp[n] << "\n";
return 0;
}
D. Unshuffling a Deck
题意
给出一个大小为 \(n\) 的排列,每次操作可以将 \(n\) 个数分为 \(1 \sim n\) 个非空连续份,然后将对称的份两两交换,试给出在 \(n\) 次操作内将排列排为升序的操作过程。
题解
- 找到值相差为 \(1\) 的逆序对:\(i<j\),\(a_i = a_j + 1\)
- 将已为升序的数视为一个整体,找到 \(t\) 满足:\(i \le t < j\),\(a_t > a_{t+1}\)
- 分为 \(4\) 份,\(D_1=[a_1,a_2,\dots,a_{i-1}],\ D_2=[a_i,a_{i+1},\dots, a_t],\ D_3=[a_{t+1},a_{t+2},\dots, a_j],\ D_4=[a_{j+1},a_{j+2},\dots, a_n]\)
- 将对称组交换,转至步骤 \(1\) 。
代码
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin >> n;
vector<int> a(n), pos(n);
for (int i = 0; i < n; i++) {
cin >> a[i];
--a[i];
}
vector<vector<int>> ans;
while (not is_sorted(a.begin(), a.end())) {
for (int i = 0; i < n; i++) {
pos[a[i]] = i;
}
//1
for (int i = 1; i < n; i++) {
if (pos[i] < pos[i - 1]) {
//2
int l = pos[i];
int r = pos[i - 1];
int mid = l;
while (a[mid + 1] == a[mid] + 1) ++mid;
//3
ans.push_back({l, mid - l + 1, r - mid, n - r - 1});
//4
vector<int> b;
for (int i = r + 1; i < n; i++) b.push_back(a[i]);
for (int i = mid + 1; i < r + 1; i++) b.push_back(a[i]);
for (int i = l; i < mid + 1; i++) b.push_back(a[i]);
for (int i = 0; i < l; i++) b.push_back(a[i]);
a.swap(b);
break;
}
}
}
cout << ans.size() << "\n";
for (auto &v : ans) {
//每份非空
while (v.back() == 0) v.pop_back();
while (v.front() == 0) v.erase(v.begin());
cout << v.size() << "\n";
for (int i = 0; i < int(v.size()); i++) {
cout << v[i] << " \n"[i == int(v.size()) - 1];
}
}
return 0;
}