AT_wtf22_day1_b Non-Overlapping Swaps 题解
特别有意思的题。
思路#
首先,由于它的限制次数还算比较多。
在我们正常最少交换次数时,我们是通过提取每一个置换环,使用置换环长度减一之和来达到的。
那么在这道题中,我们依然可以继续这么考虑,每一次交换一个置换环,然后操作一次
如何达到置换环长度减一?
我们现在称一个点
我们考虑将这个置换环上的点变成一个序列。
它在置换环上的位置是在序列上的位置。
然后我们以它的标号建一个小根笛卡尔树。
这个笛卡尔树有什么性质?
树上的每一个点都想要到达它在先序遍历下的后一个点。
那么很容易想到。
我们按后序遍历每一次交换这个点与它的父亲(跳过根)。
这样就成功了。
这个最后是正确的很好说明,自己稍微画一画就发现了。
至于操作的合法性:
- 对于一个有左儿子的点来说,操作完它后会操作它的左子树,这里面的所有点显然小于这个点。
- 对于一个没有左儿子但是有爷爷的点来说,操作完它会操作它的父亲,这显然也是合法的。
- 对于一个两者都没有的点,那么这个点显然是根的右儿子,因为当我们从小到大的枚举每一个置换环时,一个环的根一定也是这个环的第一个数,所以根没有左儿子,那么这就是最后一次操作,所以也是合法的。
代码就很好写了。
时间复杂度:
Code#
#include <bits/stdc++.h>
using namespace std;
int n;
int p[250010], v[250010];
int a[250010], f[250010], s[250010];
inline void solve() {
cin >> n;
for (int i = 1; i <= n; i++) cin >> p[i];
for (int i = 1; i <= n; i++) v[i] = f[i] = 0;
vector<pair<int, int>> ns;
for (int i = 1; i <= n; i++) {
if (v[i]) continue;
int ct = 0;
int tp = 0;
a[++ct] = i;
for (int j = p[i]; j != i; j = p[j]) a[++ct] = j;
s[++tp] = 1;
for (int j = 2; j <= ct; j++) {
int las = 0;
while (a[s[tp]] > a[j]) f[las] = s[tp], las = s[tp--];
f[las] = s[++tp] = j;
}
for (int i = 2; i <= tp; i++) f[s[i]] = s[i - 1];
if (i != 1) ns.emplace_back(1, 1);
for (int i = ct; i >= 1; i--)
if (f[i]) ns.emplace_back(minmax(a[i], a[f[i]]));
for (int i = 1; i <= ct; i++) v[a[i]] = 1;
}
cout << ns.size() << "\n";
for (auto i : ns)
cout << i.first << " " << i.second << "\n";
}
int main() {
int t;
cin >> t;
while (t--) solve();
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通