题意
给一个长度为\(N\)的排列\(P\)与一个正整数\(K\),可以进行如下操作:
对于两个满足\(|i-j|\geq K\) 且\(|P_i-P_j|=1\)的下标\(i\)和\(j\),交换\(P_i\)与\(P_j\)
我们的目的是要求操作后的排列字典序最小
解法
首先,这两个条件都不好判断,直接做是不太好搞的
考虑把这个数组映射一下(这好像是对于排列的一个常见套路?)
令\(pos[P_i]=i\),即元素\(P_i\)的位置为\(i\)
我们对\(pos\)数组进行操作,操作就转化为了:
可以交换两个相邻元素,并且相邻元素的差的绝对值\(\geq K\)
我们发现,对于两个元素\(i,j,i<j\),如果\(|i-j|<K\),那么它们的相对位置永远不会改变
也就是说无论进行多少次交换,如何交换,\(i\)一定会在\(j\)之前
这就启发我们用拓扑排序进行求解
但是我们应按照一种什么样的规则选择求出的拓扑序,使得原排列的字典序最小呢?
网上有许多题解都认为,原序列的字典序最小等价于求逆序列的字典序最小
但实际上这个想法是错误的,可以举一组反例来说明
\(rev\{2.3.1\} = \{3.1.2\}\)
\(rev\{3.1.2\} = \{2.3.1\}\)
我们可以发现,第一个排列的字典序比第二个大,但其逆序列的字典序却比第二个小
实际上,真正正确的结论是原序列的字典序最小,等价于其逆序列的反序列的字典序最大
那么接下来的任务就是建图了
为了避免重复连边,我们规定一个元素仅能向其后面的元素连边
考虑连边的意义,若存在一条有向边\(E(u,v)\),说明\(u,v\)之间的相对顺序已经确定即\(v\)在\(u\)之后
对于一个元素\(x\),它会向值域为\((x-K,x)\cup (x,x+K)\)的元素连边,并且要求这些元素的初始位置在\(x\)之后
但是这样暴力连边,边的数量是\(O(N^2)\)级别的
我们可以发现,相对顺序是有传递关系的,比如说对于三条边\(A \rightarrow B, B\rightarrow C,A\rightarrow C\),第三条边完全可以被前两条边替代,那么它就是无效的
为了去掉这些无效的边,我们规定\(x\)只向值域为\((x-K,x)\)与\((x,x+K)\)中的位置在\(x\)之后且最小的元素连边
这是因为我们可以发现由于这两个集合的大小均不超过\(K\),那么它们内部一定是互有连边的
那么我们只需要连一个位置最靠前的元素,就可以根据这个元素扩张到整个集合了
由于是求逆序列的反序列最大,我们需要建反图,并用大根堆求字典序最大的序列
找到这个元素可以用线段树实现,由于合法的元素一定在当前元素之后,所以倒序加点即可
代码
#include <queue>
#include <cstdio>
#include <cctype>
#include <cstring>
using namespace std;
const int N = 5e5 + 10;
int read();
int n, k;
int p[N], q[N];
int cap;
int head[N], deg[N], to[N << 1], nxt[N << 1];
priority_queue<int> que;
inline int min(int x, int y) {
return x < y ? x : y;
}
inline int max(int x, int y) {
return x > y ? x : y;
}
struct SegTree {
int val[N << 2];
void init() {
memset(val, 0x3f, sizeof val);
}
void change(int x, int l, int r, int k, int v) {
if (l == r) return val[x] = v, void();
int mid = l + r >> 1;
if (k <= mid)
change(x << 1, l, mid, k, v);
else
change(x << 1 | 1, mid + 1, r, k, v);
val[x] = min(val[x << 1], val[x << 1 | 1]);
}
int query(int x, int l, int r, int ls, int rs) {
if (ls <= l && r <= rs) return val[x];
int mid = l + r >> 1, res = 0x3f3f3f3f;
if (ls <= mid)
res = min(res, query(x << 1, l, mid, ls, rs));
if (rs > mid)
res = min(res, query(x << 1 | 1, mid + 1, r, ls, rs));
return res;
}
} tr;
inline void add(int x, int y) {
to[++cap] = y, nxt[cap] = head[x], head[x] = cap;
++deg[y];
}
int main() {
n = read(), k = read();
for (int i = 1; i <= n; ++i) p[i] = read();
for (int i = 1; i <= n; ++i) q[p[i]] = i;
tr.init();
for (int i = n; i >= 1; --i) {
int x = tr.query(1, 1, n, q[i], min(q[i] + k - 1, n));
if (x <= n)
add(q[x], q[i]);
int y = tr.query(1, 1, n, max(q[i] - k + 1, 1), q[i]);
if (y <= n)
add(q[y], q[i]);
tr.change(1, 1, n, q[i], i);
}
for (int i = 1; i <= n; ++i)
if (!deg[i]) que.push(i);
for (int i = n; i >= 1; --i) {
int u = que.top(); que.pop();
p[i] = u;
for (int i = head[u]; i; i = nxt[i])
if (!--deg[to[i]]) que.push(to[i]);
}
for (int i = 1; i <= n; ++i) q[p[i]] = i;
for (int i = 1; i <= n; ++i) printf("%d\n", q[i]);
return 0;
}
int read() {
int x = 0, c = getchar();
while (!isdigit(c)) c = getchar();
while (isdigit(c)) x = x * 10 + c - 48, c = getchar();
return x;
}