VP Educational Codeforces Round 62 (Rated for Div. 2)
A. Detective Book
题意:给你一个数组\(a\),从\(1\)开始,每次跳到\(a_i\),直到\(a_i = i\)算一轮,然后位置往后移一位。求总共几轮。
模拟。
点击查看代码
void solve() {
int n;
std::cin >> n;
std::vector<int> a(n);
for (int i = 0; i < n; ++ i) {
std::cin >> a[i];
-- a[i];
}
int ans = 0;
int i = 0;
while (i < n) {
++ ans;
int j = a[i];
while (i <= j) {
j = std::max(j, a[i]);
++ i;
}
}
std::cout << ans << "\n";
}
B. Good String
题意:'<'$可以删去其左边的所以字符,'>'可以删去右边所有字符。求最少删去几个字符使得可以一次操作删除字符串。
看左边连续的'<'和右边连续的'>'的长度的最小值。
点击查看代码
void solve() {
int n;
std::cin >> n;
std::string s;
std::cin >> s;
int l = 0, r = n - 1;
while (l < n && s[l] == '<') {
++ l;
}
while (r >= 0 && s[r] == '>') {
-- r;
}
std::cout << std::min(l, n - 1 - r) << "\n";
}
C. Playlist
题意:\(n\)个物品,每个物品有长度和价值,最多选\(k\)个物品,价值为总长度乘最小价值。求最大价值。
按价值从大到小排序,然后用小根堆维护前\(k\)大。如果不足\(k\)个直接选前\(k\)个就行。
如果选\(k\)个,那么就是典题,我们选择了最小价值的情况下肯定选大于等于这个价值的前\(k\)个长度最大的。如果是选了小于\(k\)个,那么也是枚举最小价值,此时因为不足\(k\)个,那么总长度越大越好,所以全部选上。
点击查看代码
void solve() {
int n, k;
std::cin >> n >> k;
std::vector<std::pair<int, int>> a(n);
for (int i = 0; i < n; ++ i) {
int t, v;
std::cin >> t >> v;
a[i] = {v, t};
}
std::ranges::sort(a, std::greater<>());
std::priority_queue<int, std::vector<int>, std::greater<int>> heap;
i64 ans = 0, sum = 0;
for (auto & [v, t] : a) {
if (heap.size() == k) {
if (heap.top() < t) {
sum = sum - heap.top() + t;
heap.pop();
heap.push(t);
}
} else {
sum += t;
heap.push(t);
}
ans = std::max(ans, sum * v);
}
std::cout << ans << "\n";
}
D. Minimum Triangulation
区间dp板子题。
点击查看代码
void solve() {
int n;
std::cin >> n;
const i64 inf = 1e18;
std::vector f(n + 1, std::vector<i64>(n + 1));
for (int len = 3; len <= n; ++ len) {
for (int i = 1; i + len - 1 <= n; ++ i) {
int j = i + len - 1;
f[i][j] = inf;
for (int k = i + 1; k < j; ++ k) {
f[i][j] = std::min(f[i][j], f[i][k] + f[k][j] + (i64)i * k * j);
}
}
}
std::cout << f[1][n] << "\n";
}
E. Palindrome-less Arrays
题意:有一个数组,值域在\([1, k]\),有些地方填了,有些没填。如果这个数组满足没有一个长度大于1的奇数回文子数组就是好的。求好数组个数。
奇数回文子数组一定有一个长度为\(3\)的回文子数组。也就是\(a_i \ne a_{i+2}\)。那么发现奇偶性相同的位置需要一起讨论。那么把原数组拆成两个数组,分别求出方案数然后相乘就行。
那么问题变成了一个数组有些地方没填要求相邻数不能相等。考虑把所有没填的连续的段拿出来,显然它和其它段互不影响,分别求出来然后乘起来就行。
那么预处理一个数组\(f[i][0/1]\)表示长度为\(i\)的没填的段两边相不相等的方案数,\(f[i][0] = (k - 1) \times f[i - 1][1], f[i][1] = (k - 2) * f[i - 1][1] + f[i - 1][0]\)。转移方程意思就是讨论在某尾放什么数。
然后还有四种特性情况,一种是没有没填的位置,这个判断就行。
一种是没填的连续段作为数组开头,那么枚举第一个数填什么,得到\(f[len - 1][0] + f[len - 1][1] * (k - 1)\)。结尾是连续段的同理。
还有就是全是没填的。那么是\(k\times(k-1)^{n-1}\)。
点击查看代码
const int mod = 998244353;
int power(int a, int b) {
int res = 1;
for (; b ; b >>= 1, a = 1LL * a * a % mod) {
if (b & 1) {
res = 1LL * res * a % mod;
}
}
return res;
}
void solve() {
int n, k;
std::cin >> n >> k;
std::vector<int> a, b;
for (int i = 0; i < n; ++ i) {
int x;
std::cin >> x;
if (i & 1) {
a.push_back(x);
} else {
b.push_back(x);
}
}
std::vector f(n + 1, std::array<int, 2>{0, 0});
f[0][0] = 0, f[0][1] = 1;
for (int i = 1; i <= n; ++ i) {
f[i][0] = (i64)(k - 1) * f[i - 1][1] % mod;
f[i][1] = ((i64)(k - 2) * f[i - 1][1] % mod + f[i - 1][0]) % mod;
}
auto work = [&](std::vector<int> & a) -> int {
int res = 1;
int n = a.size();
if (std::ranges::count(a, -1) == n) {
res = (i64)k * power(k - 1, n - 1) % mod;
return res;
}
for (int i = 0; i < n; ++ i) {
if (a[i] == -1) {
int j = i;
while (j < n && a[j] == -1) {
++ j;
}
int len = j - i;
if (i == 0 || j == n) {
res = (i64)(f[len - 1][0] + (i64)(k - 1) * f[len - 1][1] % mod) % mod * res % mod;
} else {
if (a[i - 1] == a[j]) {
res = (i64)res * f[len][0] % mod;
} else {
res = (i64)res * f[len][1] % mod;
}
}
i = j;
} else {
if (i && a[i] == a[i - 1]) {
return 0;
}
}
}
return res;
};
int ans = (i64)work(a) * work(b) % mod;
std::cout << ans << "\n";
}