第45届 2021icpc 昆明 A-AC(反悔贪心)

第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]
      1. 选 a[i], 不选择 a[i - 1] 和 a[i + 1]
      2. 选 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

当我们将 354 进行缩点时, 
中心点 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;
}

END

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值