题解:洛谷 AT_abc470_d Inverse and Swap
【题目来源】
洛谷:AT_abc470_d [ABC470D] Inverse and Swap
【题目描述】
给定一个长度为 \(N\) 的排列 \(P = (P_1, \dots, P_N)\),其为 \(1\) 到 \(N\) 的一个排列。
你需要按顺序处理 \(Q\) 个操作。操作有以下两种类型:
1 x y:交换 \(P_x\) 与 \(P_y\) 的值。2:构造排列 \(P' = (P'_1, \dots, P'_N)\),其为 \(1\) 到 \(N\) 的一个排列,满足如下条件(可以证明这样的 \(P'\) 唯一存在):- 对于 \(1 \leq i \leq N\),都有 \(P_{P'_i} = i\)。
然后用 \(P'_1,\dots, P'_N\) 的值依次替换 \(P_1,\dots,P_N\)。
- 对于 \(1 \leq i \leq N\),都有 \(P_{P'_i} = i\)。
输出经过所有操作后 \(P_1,\dots,P_N\) 的值,数值之间用空格分隔输出在同一行。
【输入】
输入按如下格式从标准输入读取:
\(N\) \(Q\)
\(P_1\) \(P_2\) \(\cdots\) \(P_N\)
\(\mathrm{query}_1\)
⋮
\(\mathrm{query}_Q\)
其中,\(\mathrm{query}_q\) 表示第 \(q\) 个操作,按以下两种格式之一给出:
\(1\) \(x\) \(y\)
\(2\)
【输出】
输出所有操作之后 \(P_1,\dots,P_N\) 的值。数之间用空格分隔输出在一行。
【输入样例】
5 5
2 1 3 5 4
1 2 4
2
1 2 3
1 3 4
2
【输出样例】
4 5 2 1 3
【核心思想】
-
问题分析:给定排列 \(P\) 和 \(Q\) 个操作,操作 \(1\) 交换两个位置的值,操作 \(2\) 将 \(P\) 替换为其逆排列 \(P'\)(满足 \(P_{P'_i} = i\))。若每次操作 \(2\) 都显式求逆,复杂度为 \(O(N)\),总复杂度 \(O(NQ)\) 无法接受。核心观察是:操作 \(2\) 等价于切换"当前视角"——在逆排列上执行交换操作,等价于在原排列的逆上执行对应位置的交换。
-
算法选择:
- 双数组维护 + 延迟求逆:同时维护原排列 \(a\) 和逆排列 \(b\),用布尔标记 \(flag\) 记录当前处于"原排列模式"还是"逆排列模式"
- 操作 \(2\) 的 \(O(1)\) 优化:不真正执行求逆,而是翻转 \(flag\),后续操作根据 \(flag\) 决定操作哪个数组
-
关键步骤:
- 初始化:读入 \(P\),\(a[i] = P_i\),\(b[a[i]] = i\)(\(b\) 是 \(a\) 的逆排列),\(flag = false\)
- 操作 \(1\)(交换 \(x, y\)):
- 若 \(!flag\)(操作原排列 \(a\)):
- \(A = a[x], B = a[y]\),交换 \(a[x], a[y]\)
- 更新逆排列:\(b[A] = y, b[B] = x\)
- 若 \(flag\)(操作逆排列 \(b\)):
- \(A = b[x], B = b[y]\),交换 \(b[x], b[y]\)
- 更新原排列:\(a[A] = y, a[B] = x\)
- 若 \(!flag\)(操作原排列 \(a\)):
- 操作 \(2\)(求逆):\(flag = !flag\)(\(O(1)\) 切换模式)
- 输出:若 \(!flag\) 输出 \(a\),否则输出 \(b\)
-
时间/空间复杂度:
- 时间复杂度:\(O(Q)\),每次操作 \(O(1)\)
- 空间复杂度:\(O(N)\),两个排列数组
-
延迟求逆的核心思想:
- 对偶性利用:若当前处于逆排列模式,交换操作 \(1\) 的语义自然对应于逆排列上的交换,只需同步维护另一个数组
- 奇偶性决定最终输出:操作 \(2\) 执行偶数次等于无操作,奇数次等于执行一次,用 \(flag\) 记录奇偶性
- 避免显式求逆:\(O(N)\) 的求逆被 \(O(1)\) 的模式切换替代,通过操作语义的对偶性保证正确性
- 适用于频繁求逆/翻转、需要维护双向映射的数据结构问题
【算法标签】
普及 #模拟
【代码详解】
#include <bits/stdc++.h>
using namespace std;
const int N = 500005; // 最大排列长度
int n, q; // n:排列长度, q:查询数量
int a[N], b[N]; // a[i]:排列P中位置i的值, b[i]:值i所在的位置(即b是a的逆排列)
int main()
{
cin >> n >> q; // 读入排列长度和查询数量
for (int i=1; i<=n; i++) // 读入排列P
{
cin >> a[i]; // a[i]表示位置i上的值
b[a[i]] = i; // b[v]表示值v所在的位置,建立逆排列关系
}
bool flag = false; // flag标记当前是否处于"逆排列模式"
// flag=false时操作的是原排列a,flag=true时操作的是逆排列b
while (q--) // 循环处理每个查询
{
int op, x, y; // op:操作类型, x,y:操作参数
cin >> op; // 读入操作类型
if (op==1) // 操作1:交换两个位置的值
{
cin >> x >> y; // 读入要交换的两个位置
if (!flag) // 当前操作原排列a
{
int A = a[x], B = a[y]; // 记录位置x和y上的值
swap(a[x], a[y]); // 交换a中位置x和y的值
// 更新逆排列b:值A现在到了位置y,值B现在到了位置x
b[A] = y, b[B] = x;
}
else // 当前操作逆排列b(相当于对原排列求逆后再操作)
{
int A = b[x], B = b[y]; // 记录"值"x和y所在的位置
swap(b[x], b[y]); // 交换b中"值"x和y的位置信息
// 更新原排列a:位置A上的值变为y,位置B上的值变为x
a[A] = y, a[B] = x;
}
}
else // 操作2:对当前排列求逆(即构造满足P_{P'_i}=i的P')
{
flag = !flag; // 切换模式:原排列<->逆排列
// 利用flag的切换来延迟执行求逆操作,避免O(N)的显式求逆
}
}
if (!flag) // 如果最终flag为false,直接输出原排列a
{
for (int i=1; i<=n; i++)
cout << a[i] << " ";
cout << endl;
}
else // 如果最终flag为true,说明进行了奇数次求逆,输出逆排列b
{
for (int i=1; i<=n; i++)
cout << b[i] << " ";
cout << endl;
}
return 0;
}
【运行结果】
5 5
2 1 3 5 4
1 2 4
2
1 2 3
1 3 4
2
4 5 2 1 3
浙公网安备 33010602011771号