题解:CF2069D Palindrome Shuffle
线性做法
特判掉原串是回文串,答案是
先删掉首尾相等的字符,因为一定不会操作到这里。此时假设字符串的长度为
现在这个字符串首尾不相等,则选择的区间一定包含首或包含尾(不然首尾仍然不相等,不能回文),因此选择的区间一定是当前字符串的前缀或后缀。
然后我们先找到中间最长的回文串。比如字符串是 abcababac,我们会找到 abc[aba]bac。此时如果两侧每个字符的出现次数都相等,那么我们只需重排其中一侧即可。上面我们只需重排左侧的 abc 或右边的 bac 即可。
如果出现次数不相等,比如 aa[bb]cc,我们直接枚举前缀和后缀的长度即可。这个长度一定超过 aab 是不可行的。
我们现在枚举了一个后缀
比较简便的方法就是:如果每个字符在
简单证明一下。因为题目保证有解,所以至多有一个字符出现了奇数次。
如果没有字符出现了奇数次,则我们在
如果有一个字符出现了奇数次,设出现了
所以这个判断方法是正确的。我们用两个桶记录,枚举时维护前缀和后缀每个字符的出现个数,到最后一个合法的位置即可。
代码
C++ 20 大法好。
#include <cassert>
#include <cmath>
#include <cstdint>
#include <cstdlib>
#include <iostream>
#ifndef ONLINE_JUDGE
#include <format>
#define debug(...) std::cerr << std::format(__VA_ARGS__)
#else
#define debug(...)
#endif
template<typename T>
void ckmax(T& a, T b) {
if (a < b) a = b;
}
template<typename T>
void ckmin(T& a, T b) {
if (b < a) a = b;
}
#include <algorithm>
#include <string>
#include <ranges>
#include <vector>
void solve() {
std::string s;
std::cin >> s;
std::ranges::for_each(s, [&](char& c) {c -= 'a';});
int n = static_cast<int>(s.size());
if (std::ranges::equal(s, s | std::views::reverse)) { // 特判回文串
std::cout << "0\n";
return;
}
// 将首尾相同的部分删掉
int len = *std::ranges::find_if_not(std::views::iota(0, n), [&](int i) {return s[i] == s[n - i - 1];}); // 找到第一个不满足的下标
s.erase(s.begin(), s.begin() + len);
s.erase(s.end() - len, s.end());
n -= len << 1;
// 找出中间回文的长度
int midl = (n - 1) >> 1, midr = n >> 1;
len = *std::ranges::find_if_not(std::views::iota(0, n), [&](int i) {return s[midl - i] == s[midr + i];}); // 找到第一个不满足中间回文的长度
midl -= len;
midr += len;
// [midl + 1, midr - 1] 这一段是回文的
// 判断 [0, midl] 和 [midr, n) 每个字符的出现次数是否相同
std::string lef = s.substr(0, midl + 1), rig = s.substr(midr);
std::vector<int> b1(26, 0), b2(26, 0);
std::ranges::for_each(lef, [&](char c) {++b1[c];});
std::ranges::for_each(rig, [&](char c) {++b2[c];});
if (b1 == b2) { // 相同,则只需重排一侧即可
std::cout << lef.size() << "\n";
return;
}
// 此时一定选一个前缀或一个后缀,并且要跨过中间的回文区
std::vector<int> buc(26, 0), chosen(26, 0);
std::ranges::for_each(s, [&](char c) {++buc[c];});
int ans = n;
// 选后缀,保留左边,枚举保留几位,找到第一个不满足的
int i = *std::ranges::find_if(std::views::iota(0, (n + 1) / 2), [&](int i) {
--buc[s[i]];
++chosen[s[i]];
return buc[s[i]] < chosen[s[i]]; // 代表不满足
}) - 1;
ckmin(ans, n - i - 1);
std::ranges::fill(buc, 0);
std::ranges::fill(chosen, 0);
std::ranges::for_each(s, [&](char c) {++buc[c];});
// 保留右边
i = *std::ranges::find_if(std::views::iota(0, (n + 1) / 2), [&](int i) {
--buc[s[n - i - 1]];
++chosen[s[n - i - 1]];
return buc[s[n - i - 1]] < chosen[s[n - i - 1]];
}) - 1;
ckmin(ans, n - i - 1);
std::cout << ans << "\n";
}
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int t;
std::cin >> t;
while (t--) {
solve();
}
return 0;
}