【贪心+构造】codeforces 2209 D. Ghostfires
题目
https://codeforces.com/contest/2209/problem/D
题意
第一行输入一个正整数 \(T(1 \leq T \leq 10^4)\),每个测试用例给定三个整数 \(r,g,b(0 \leq r,g,b \leq 10^6,r+g+b > 0)\),代表有 \(r\) 个字母 R,\(g\) 个字母 G,\(b\) 个字母 B。问:你最多能构造多长的字符串,满足任何相邻位置字母不同,且相隔距离为 \(3\) 的也不同(比如 \(RGBR,RR\) 都不满足),输出任意一个最长的字符串。
题解
对三种元素的数量进行从高到低排序,不妨将排序后的元素记为 \(A,B,C\) 数量分别为 \(a, b, c\)。
若数量最多的元素 \(A\) 的数量 \(a\) 大于等于其它两种元素 \(B,C\) 的数量 \(b,c\) 之和,则可以按以下方式进行构造:
- 若只有一种元素存在,即 \(b==0\),则直接输出一个元素 \(A\);
- 否则先重复输出 \(AB\),直至 \(B\) 的数量 \(b\) 用尽,随后交替输出 \(A\) 和 \(C\),直到有元素的数量用尽。
若数量最多的元素 \(A\) 的数量 \(a\) 小于其它两种元素 \(B,C\) 的数量 \(b,c\) 之和,则可以按以下方式进行构造:
- 首先以二分法计算出 \(num\),使得 \(a - num == b + c - num * 2\) 能够成立
随后遍历所有三元组为开头的情况,由于只有 \(3\) 种不同的元素,所以其实只有 \(9\) 种不同的三元组(指使用三个元素构成一组,组内元素各不相同)
当输出完三元组后,就可以按照 \(ABAB...ACAC...、BABA...CACA...或CACA...BABA...\) 的形式构造出答案,开头的元素取决于已经构造的字符串倒数第二个元素是哪个字符。
参考代码
#include<bits/stdc++.h>
using namespace std;
constexpr int N = 3;
constexpr string SERIALS[2] = {"RGBGBRBRG", "RBGBGRGRB"};
int T = 1;
int a[N], b[N], idx[N];
void solve() {
string ans;
auto &S = SERIALS[0];
for (int i = 0; i < 3; ++ i) cin >> a[i], idx[i] = i;
sort(idx, idx + 3, [&](int i, int j) {
return a[i] > a[j];
});
if (a[idx[1]] == 0) {// 只有一种字符,那么最长的字符串只有一个字符
cout << S[idx[0]] << '\n';
return ;
}
if (a[idx[0]] >= a[idx[1]] + a[idx[2]]) {// 数量最多的字符大于等于其它两种字符加起来的数量
// 这种情况一定是 ABABAB...ACAC...(A) 的形式
// 其中 A 是数量最多的字符,B、C是另外两种字符
while (a[idx[1]] -- && a[idx[0]] --) cout << S[idx[0]] << S[idx[1]];
while (a[idx[0]] --) {
cout << S[idx[0]];
if (a[idx[2]] --) cout << S[idx[2]];
else break;
}
cout << '\n';
return ;
}
// 求一元一次方程 a[idx[0]] - num = a[idx[1]] + a[idx[2]] - 2 * num
int num = a[idx[1]] + a[idx[2]] - a[idx[0]];
for (int i = 0; i < 3; ++ i) a[i] -= num;// 每种元素的个数要扣去已经组成三个元素为一组的组数
memcpy(b, a, sizeof a);// 备份数组 a
for (auto &it: SERIALS) {// 遍历每种三个元素为一种的可能性
for (int i = 0; i < 3; ++ i) {// 遍历出所有三元组为开头的情况
memcpy(a, b, sizeof b);// 还原数组 a
string res;
// 以字符串 it 的下标 k 为起始点,连续放入 num 个三元组
// 当遍历到结尾的时候,还要遍历下一个位置就回到最左侧的下标位置
for (int j = 0, k = i * 3; j < num; ++ j, k = (k + 3) % 9) {
res.push_back(it[k]);
res.push_back(it[k + 1]);
res.push_back(it[k + 2]);
}
// 若存在三元组,且已构造的字符串倒数第二个字符不是初始时数量最多的那种字符
// 则可以将初始时数量最多的字符视为 A,其它两种字符视为 B、C,构造出 ABABAB...ACAC...
if (!res.empty() && res[res.size() - 2] == S[idx[0]]) {
for (int p = 1; p <= 2; ++ p) {
while (a[idx[0]] > 0 && a[idx[p]] > 0) {
res.push_back(S[idx[0]]);
res.push_back(S[idx[p]]);
-- a[idx[0]], -- a[idx[p]];
}
}
} else {
// 首先用 pos 维护出倒数第二个元素
int pos = idx[1];
if (!res.empty()) {// 已经存在构造的字符串
for (int p : idx) {
if (S[p] == res[res.size() - 2]) {// 跟已构造的字符串倒数第二个元素相同
pos = p;
break;
}
}
}
if (res.empty() || (S[pos] != res.back() && S[pos] != res[res.size() - 3])) {// 放入新的元素不会造成冲突
while (a[pos] > 0 && a[idx[0]] > 0) {
res.push_back(S[pos]);
res.push_back(S[idx[0]]);
-- a[pos], -- a[idx[0]];
}
for (int i = 0; i < 3; ++ i) {
if (i != pos && i != idx[0]) {
if (res.empty() || (S[i] != res.back() && S[i] != res[res.size() - 3])) {// 放入新的元素不会造成冲突
while (a[i] > 0 && a[idx[0]] > 0) {
res.push_back(S[i]);
res.push_back(S[idx[0]]);
-- a[i], -- a[idx[0]];
}
}
break;
}
}
}
}
if (ans.size() < res.size()) ans = res;// 若答案更优,则更新答案
}
}
cout << ans << '\n';
}
int main() {
ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
cin >> T;
while (T --) {
solve();
}
return 0;
}
浙公网安备 33010602011771号