[题目链接]
https://atcoder.jp/contests/agc001/tasks/agc001_f
[题解]
题目中的限制比较麻烦 , 首先转化问题 , 考虑排列的逆 \(Q = P ^ {-1}\)。
那么 , \(Q\) 中相邻元素差不超过 \(K\) 的都可以任意互换。
考虑两个元素差值 \(|u - v| < K\) , 根据上面所述 , \(u\) 和 \(v\) 在 \(Q\) 中的相对顺序永远不会改变。
一个重要的事实是 : 设一个新序列 \(R\) , 只要 \(Q , R\) 相对顺序关系不改变 , 那么就必然存在一个有限步的操作序列 , 使 \(Q\) 转化为 \(R\)。 这是因为必然能在 \(Q\) 中找到相邻两个元素 , 交换后使逆序对数减少了 \(1\)。
将这个条件对应到 \(P\) 上 , 原限制转化为对于 \(|i - j| \leq K , P_{i} \leq P_{j}\) , 必然有 \(i\) 的值比 \(j\) 的值小。 反之亦然。
所有这样的关系可以构成一张有向无环图 (\(DAG\)) , 要求的就是求出一个拓扑序列将节点编号 , 并使得字典序尽可能小。
注意到出度为 \(0\) 的点中最大编号点可以直接填当前的最大值 , 那么用堆维护点做一遍反向拓扑排序即可。 时间复杂度 \(O(N ^ 2)\)。
这样仍然不能通过本题。 考虑用数据结构优化这个过程。
首先建一棵线段树维护 \(P\) 值。
对于一个点 \(i\) , 若 \((i - K , i + K)\) 中的最大值是 \(P_{i}\) , 那么这个点入队。
每次从堆中弹出一个点 , 相当于将它的权值赋为 \(-\infty\)。 那么从两边找到新的出度为 \(0\) 的点 , 继续执行上述过程即可。
时间复杂度 : \(O(NlogN)\)
[代码]
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int MN = 5e5 + 5 , INF = 2e9;
int N , K , P[MN] , mxp[MN << 2] , Ans[MN] , inq[MN];
priority_queue < int > pq;
inline void build(int now , int l , int r) {
if (l == r) { mxp[now] = l; return; }
int mid = l + r >> 1;
build(now << 1 , l , mid) , build(now << 1 | 1 , mid + 1 , r);
mxp[now] = P[mxp[now << 1]] > P[mxp[now << 1 | 1]] ? mxp[now << 1] : mxp[now << 1 | 1];
}
inline int Query(int i, int l, int r, int a, int b) {
if (r < a || b < l) return 0;
if (a <= l && r <= b) return mxp[i]; int mid = l + r >> 1;
int v1 = Query(i << 1, l , mid , a, b), v2 = Query(i << 1 | 1, mid + 1 , r , a, b);
return P[v1] > P[v2] ? v1 : v2;
}
inline void Del(int now , int l , int r , int x) {
if (l == r) { mxp[now] = 0; return; }
int mid = l + r >> 1;
if (mid >= x) Del(now << 1 , l , mid , x);
else Del(now << 1 | 1 , mid + 1 , r , x);
mxp[now] = P[mxp[now << 1]] > P[mxp[now << 1 | 1]] ? mxp[now << 1] : mxp[now << 1 | 1];
}
inline void check(int id) {
if (inq[id]) return;
if (Query(1, 1, N, id - K + 1, id + K - 1) == id)
pq.push(id) , inq[id] = 1;
}
int main() {
scanf("%d%d" , &N , &K);
for (int i = 1; i <= N; ++i) scanf("%d" , &P[i]);
P[0] = -INF;
build(1 , 1 , N);
for (int i = 1; i <= N; ++i) check(i);
for (int i = N; i >= 1; --i) {
int u = pq.top(); pq.pop();
Ans[u] = i;
Del(1 , 1 , N , u);
int pos;
if ((pos = Query(1 , 1 , N , u - K + 1 , u - 1))) check(pos);
if ((pos = Query(1 , 1 , N , u + 1 , u + K - 1))) check(pos);
}
for (int i = 1; i <= N; ++i) printf("%d\n" , Ans[i]);
return 0;
}