题目描述
难度分:1500
输入T(≤104)表示T组数据。所有数据的n之和≤2×105。
每组数据输入n(1≤n≤2×105),k(1≤k≤2×105)和两个长为n的字符串s和t,只包含小写字母。
你可以执行如下操作任意次:
- 选择下标i和j,交换s[i]和s[j],要求|i−j|=k或者|i−j|=k+1。
能否把s变成t?输出YES
或NO
。
输入样例
7
6 3
talant
atltna
7 1
abacaba
aaaabbc
12 6
abracadabraa
avadakedavra
5 3
accio
cicao
5 4
lumos
molus
4 3
uwjt
twju
4 3
kvpx
vxpk
输出样例
YES
YES
NO
YES
NO
YES
NO
算法
并查集+贪心
把索引绝对差为k或k+1的索引对用并查集合并到一个组里面,构建一个映射“组id→字母→该字母在s中的索引集合”mp。
然后遍历i∈[1,n],只要发现s[i]≠t[i],就在mp[rootId][t[i]](其中rootId是i的根节点)中二分查找是否存在满足j>i的j,存在就交换s[i]和s[j]。
最后检查一下s和t是否相等即可。
复杂度分析
时间复杂度
遍历i∈[1,n]检查s[i]和t[i]是否相等的时间复杂度为O(n)。如果s[i]≠t[i],需要进行交换操作,找到j的时间复杂度是O(log2n),瓶颈在于二分查找。因此,算法整体的时间复杂度为O(nlog2n)。
空间复杂度
除了输入的s串和t串,空间消耗的瓶颈在于并查集的根节点数组p,额外空间复杂度为O(n)。
C++ 代码
#include <iostream>
#include <cstring>
#include <algorithm>
#include <unordered_map>
#include <set>
using namespace std;
int T, n, k;
const int N = 200010;
char s[N], t[N];
int p[N];
int find(int x) {
if(p[x] != x) p[x] = find(p[x]);
return p[x];
}
void merge(int x, int y) {
int rx = find(x), ry = find(y);
if(rx != ry) {
p[rx] = ry;
}
}
int main() {
scanf("%d", &T);
while(T--) {
scanf("%d%d", &n, &k);
scanf("%s", s + 1);
scanf("%s", t + 1);
for(int i = 1; i <= n; i++) {
p[i] = i;
}
for(int i = 1; i + k <= n; i++) {
merge(i, i + k);
if(i + k + 1 <= n) {
merge(i, i + k + 1);
}
}
unordered_map<int, unordered_map<char, set<int>>> mp;
for(int i = 1; i <= n; i++) {
int root = find(i);
mp[root][s[i]].insert(i);
}
bool flag = true;
for(int i = 1; i <= n; i++) {
if(s[i] == t[i]) continue;
int root = find(i);
if(!mp[root].count(t[i])) {
flag = false;
break;
}
auto& st = mp[root][t[i]];
auto it = st.upper_bound(i);
if(it == st.end()) {
flag = false;
break;
}
int j = *it;
mp[root][t[i]].erase(j);
mp[root][s[i]].erase(i);
mp[root][s[i]].insert(j);
swap(s[i], s[j]);
}
if(flag) {
for(int i = 1; i <= n; i++) {
if(s[i] != t[i]) {
flag = false;
break;
}
}
}
if(flag) puts("YES");
else puts("NO");
}
return 0;
}