模拟
进行 $n$ 组,每组不超过 $2$ 个操作,第 $k$ 组操作可以使 $a_{n - k + 1} = b_{n - k + 1}, k \in [1, n]$,总共进行不超过 $2n$ 个操作就能使 $a = b$。具体来说,若当前正在进行第 $k$ 组操作,若 $a_1 = b_{n - k + 1}$,则将 $a_1$ 反转,否则不做操作。接着,将 $a[1:n - k + 1]$ 取反并反转。
实现时,不能真的逐位操作,否则会超时,只需维护剩余待操作序列 $a[h:t]$ 的位置,序列为顺序还是逆序,序列经过了几次取反(用于确定序列头的值)。
时间复杂度 $O(n)$。
#include <iostream>
#define endl "\n"
using namespace std;
constexpr int N = 100010;
int n;
char a[N];
char b[N];
int ans[N << 1];
void solve() {
cin >> n >> a >> b;
int d = 1, h = 0, t = n - 1, k = 0;
for (int r = n - 1; ~r; r--) {
char bit = a[h];
if (d < 0)
bit = (bit - '0' ^ 1) + '0';
if (bit == b[r])
ans[k++] = 0;
ans[k++] = (t - h) * d;
int nt = h + d;
h = t;
t = nt;
d = -d;
}
cout << k << " ";
for (int i = 0; i < k; i++)
cout << ans[i] + 1 << " ";
cout << endl;
}
int main() {
cin.tie(0)->sync_with_stdio(0);
int t;
cin >> t;
while (t--)
solve();
return 0;
}
更多性质
- 按位取反是可逆的,反转字符串也是可逆的,两者结合到一起也是可逆的,即对于同一前缀,进行偶数次操作等同于没有操作。因此,可将 $a, b$ 都变成全零,再将对 $b$ 的操作序列逆序应用到 $a$ 上,就能使 $a$ 变成 $b$。
- 当前缀里所有字母都相同时,反转字符串是平凡的,即没有任何效果,且按位取反后所有字母仍然相同,即性质可以保留。因此对于某个字符串,可以找到只包含相同字母的最长前缀,如果前缀长度不等于整个字符串长度,则对该前缀执行操作就能使只包含相同字母的最长前缀长度加一,最多经过 $n - 1$ 轮操作就能使整个字符串只包含相同字母。如果此时它是全一,再对整个字符串操作一次,就能使它变成全零。
因此最多只需 $2n$ 次操作就能使 $a = b$。
时间复杂度 $O(n)$。
#include <iostream>
#include <vector>
#define endl "\n"
using namespace std;
constexpr int N = 100010;
int n;
char a[N];
char b[N];
void solve() {
cin >> n;
cin >> a >> b;
vector<int> opa, opb;
for (int i = 0; i < n - 1; i++) {
if (a[i] != a[i + 1])
opa.push_back(i + 1);
if (b[i] != b[i + 1])
opb.push_back(i + 1);
}
if (a[n - 1] != b[n - 1])
opa.push_back(n);
cout << opa.size() + opb.size() << " ";
for (int i = 0; i < opa.size(); i++)
cout << opa[i] << " ";
for (int i = opb.size() - 1; ~i; i--)
cout << opb[i] << " ";
cout << endl;
}
int main() {
cin.tie(0)->sync_with_stdio(0);
int t;
cin >> t;
while (t--)
solve();
return 0;
}