第45届icpc 昆明 A-AC(反悔贪心)
题目来源:第45届icpc 昆明 A-AC
题意:
给一个长度为 n 的字符串, 最多有 k 次修改字符串的机会, 每一次只能将任意一位字符修改为其他字符, 输出修改后的字符串最多有多少个"ac", 并输出字符串
思路:
- 通过数组 a[i] 记录将第 i 位和第 i + 1 位变为 “ac” 所需的最小步数, 如果选择 a[i] 后就不能选择 a[i - 1] 和 a[i + 1]
- 假设 a[i] 是所有 a 中最小的, 那么我们要么只选 a[i], 不选择 a[i - 1] 和 a[i + 1], 要么选择 a[i - 1] 和 a[i + 1], 不选择 a[i]
-
- 选 a[i], 不选择 a[i - 1] 和 a[i + 1]
- 选 a[i - 1] 和 a[i + 1], 不选择 a[i]
- 很显然第二种情况的更改次数小于等于第一次的, 因此第二种情况更优
-
因此问题就转换成了, 在数组 a 中选择尽可能多的元素, 保证两两不相邻,并且元素之和小于 k.
那么怎么处理这种问题呢?
- 通过小顶堆维护数组 a, 每次选出最小的以后, 删除最小值, 并用 now 记录当前已经修改过的次数, now += a[i]
- 因为我们已经选了 a[i], 那么就不能选 a[i - 1] 和 a[i + 1], 因此我们将 a[i - 1], a[i] 和 a[i + 1] 进行缩点 : l[i] = l[l[i]], r[l[i]] = i, r[i] = r[r[i]], l[r[i]] = i [缩点注释], 之后要么选择 a[i - 1] 和 a[i + 1] 并且将选择的 a[i] 删除, 要么就不选他们.
- 那么什么时候选 a[i - 1] 和 a[i + 1] 并且将选择的 a[i] 删除呢?
- 就是当 a[i - 1] + a[i + 1] - a[i] 是小顶堆的根节点, 也就是说是剩余点的最小值时, 我们才去选择它们
- 因此我们每次还需要将 a[i] = a[i - 1] + a[i + 1] - a[i] 推入优先队列中, 并且将已经缩的点进行标记
- 但还让我们输出修改后的字符串, 所以我们还需要开一个数组用来记录哪些点是被缩点后的中心点, 同时开两个数组 pl 和 pr, pl[i] 记录第 i 个点缩点后左边的点是哪个, pr[i] 记录第 i 个点缩点后右边的点是哪个
- 假设 pl[i] = i - x, 那么pr[i] 一定等于 i + x, 他们代表的意思就是我们选择了 a[i - x + 1], a[i - x + 3], …, a[i], …, a[i + x - 3], a[i + x - 1]
- 所以我们从 j = pl[i] + 1开始, 令s[j] = ‘a’, s[j + 1] = ‘c’, 每次 j += 2, 直到 j < pr[i] 为止
[缩点注释]
假设我们有一个数组a
下标i: 1 2 3 4 5 6
当我们将 3 和 5 与 4 进行缩点时,
中心点 4 左边的点就变成了 2, 右边的点就变成了 6,
而 2 右边的点就变成了 4, 6 左边的点也变成了 4
AC代码
#include <bits/stdc++.h>
#define endl "\n"
#define rep(i, m, n) for (int i = (m); i <= (n); ++i)
#define rrep(i, m, n) for (int i = (m); i >= (n); --i)
#define IOS ios::sync_with_stdio(0); cin.tie(0);
using namespace std;
typedef long long ll;
typedef pair<int, int> PII;
const int N = 5e5 + 10, mod = 1e9 + 7;
struct tree {
int num, idx;
bool operator < (const tree& a) const { return num > a.num; }
};
priority_queue<tree> q;
int n, k, a[N];
// pl记录下标为i的点缩点到左边哪个点 pr为右边的缩点
int l[N], r[N], pl[N], pr[N];
bool vis[N], mp[N];// 第i个点是否被缩点 第i个点是否是被缩点后的中心点
ll ans, now; // 记录答案 记录当前已经修改多少次
char s[N];
void solve() {
cin >> n >> k;
scanf("%s", s + 1);
rep(i, 1, n - 1) {
// a数组用于记录将s[i]s[i + 1]修改为"ac"需要修改的次数
q.push({ a[i] = (s[i] != 'a') + (s[i + 1] != 'c'), i });
pl[i] = i, pr[i] = i; l[i] = i - 1, r[i] = i + 1;
}
a[n] = a[0] = INT_MAX / 100; pl[n] = pr[n] = n; // 边界修改数开大点
while (!q.empty()) {
while (vis[q.top().idx]) q.pop();
int x = q.top().idx; q.pop();
if (now + a[x] > k) break;
ans++; now += a[x];
a[x] = a[l[x]] + a[r[x]] - a[x];
vis[l[x]] = vis[r[x]] = mp[x] = 1; // 记录l[x]和r[x]已经缩点 x点可以修改
pl[x] = pl[l[x]], pr[x] = pr[r[x]];
l[x] = l[l[x]], r[l[x]] = x;
r[x] = r[r[x]], l[r[x]] = x;
q.push({ a[x], x });
}
printf("%lld\n", ans);
rep(i, 1, n) {
if (mp[i] && !vis[i]) // 被修改过 并且 没缩点
for (int j = pl[i] + 1; j < pr[i] && j < n; j += 2)
s[j] = 'a', s[j + 1] = 'c';
}
printf("%s\n", s + 1);
}
int main() {
solve();
return 0;
}