题解:P15502 [ICPC 2025 APC] Book Sorting
1234567890regis · · 题解
P15502 [ICPC 2025 APC] Book Sorting 题解
好题!来一发不一样的题解。
题目大意
给定一个排列,你有三种操作,最后要求其变成递增序列(
- 交换相邻两项
- 将某一项放最前面
- 将某一项放最后面
求最少操作数。
题目解法
结论
证明: 只考虑操作二,操作三同理。设
那么假如有一个数
因此,
- 假如某个数
x 进行过操作二,那么它至少贡献1 次操作。 - 假如某个数
x 没有进行过操作二,那么它与l 之间至少要进行1 次操作1 ,至少贡献1 次操作。
但是,这样是不优的,因为我们可以直接对
因此,我们可以不妨假设操作二一定对值为
定义
举个例子,假设原序列为
特别地,当
结论
证明:
于是乎,我们得到了一个似乎是
定义
结论
证明: 即
因为有关
而这是显然的,因为
定义
结论
这是四边形不等式的经典结论,即决策单调性。略证一下:
使用反证法,假设
-
g(l_1, r) \le g(l_2, r) -
两式相加即和四边形不等式矛盾。
于是,我们可以使用决策单调性优化。那么,我们只需要快速求出
简单口糊一下,自己想可能更容易明白。用树状数组维护原数组每个位置对应的数的值在不在区间
复杂度
最终复杂度
注意这里并不是普通的莫队的
AC 代码
#include <iostream>
#include <vector>
#include <cstring>
#include <algorithm>
#define int long long
#define inf 1e9
#define lb(w) (w & -w)
#define debug(x) cerr << #x << ": " << x << endl
using namespace std;
const int MAXN = 1e6 + 7;
int a[MAXN], pos[MAXN], tree[MAXN], n;
void add(int idx, int val) { for (; idx <= n; idx += lb(idx)) tree[idx] += val; }
int query(int idx) { int cnt = 0; for (; idx; idx -= lb(idx)) cnt += tree[idx]; return cnt; }
struct MO
{
int l = 1, r = 0, ans;
void addR() {
ans += query(n) - query(pos[++r]);
add(pos[r], 1);
}
void addL() {
ans += query(pos[--l]);
add(pos[l], 1);
}
void delR() {
add(pos[r], -1);
ans -= query(n) - query(pos[r--]);
}
void delL() {
add(pos[l], -1);
ans -= query(pos[l++]);
}
int calc(int L, int R) {
while (r < R) addR();
while (l > L) addL();
while (r > R) delR();
while (l < L) delL();
return ans;
}
} mo;
int ANS = inf;
int f(int l, int r) {
if (l == r + 1) return n;
if (l > r) return inf;
return (l - 1) + (n - r) + mo.calc(l, r);
}
void solve(int l, int r, int optl, int optr) {
if (l > r) return;
int mid = l + r >> 1, optm = optl, ans = inf;
for (int i = optl; i <= optr; i++) {
int F = f(i, mid);
if (F < ans) ans = F, optm = i;
}
ANS = min(ANS, ans);
solve(l, mid - 1, optl, optm);
solve(mid + 1, r, optm, optr);
}
signed main()
{
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i], pos[a[i]] = i;
solve(1, n, 1, n);
cout << ANS << '\n';
}