agc01F Wide Swap

考虑一个排列的反排列($p_i=a$,则$q_a=i$,q为p的反排列)。元排列的问题就能变为在反排列中每次交换相邻的两个数,并且它们要满足差不小于k,使得最终1的位置尽量靠前,然后2的位置尽量靠前,依此类推。

记元排列为P,反排列为Q,最小字典序的Q一定对应最小字典序的P,求出最小字典序后在i前面的数一定都是比i小或者比i大但位置差小于k且在原来Q中就在i前面的数。这样的话就能建一张图表示最终排列里的前后位置关系,然后求这个图的最小字典序的一个拓扑序即可。

如果每个点都这样建边的话那就是$n^2$的了,每个点只需要向前后最先满足条件的点建边即可,其他的其实不需要。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#include <bits/stdc++.h>
using namespace std;
inline int readint()
{
char c;int tmp=0,x=1;c=getchar();
while(!isdigit(c)){if(c=='-') x=-1;c=getchar();}
while(isdigit(c)){tmp=tmp*10+c-'0';c=getchar();}
return tmp*x;
}
const int maxn=500000+5;
int p[maxn],q[maxn],deg[maxn],ans[maxn],n,k;
set<int > ss;
typedef set<int >::iterator seto;
vector<int > g[maxn];
void addedge(int from,int to)
{
g[from].push_back(to);
deg[to]++;
}
priority_queue<int ,vector<int >,greater<int > > pq;
int main()
{
n=readint();k=readint();
for(int i=1;i<=n;i++) q[p[i]=readint()]=i;
seto ite;
for(int i=1;i<=n;i++){
if(i-k>=1) ss.erase(p[i-k]);
ite=(ss.insert(p[i])).first;
if(++ite!=ss.end()) addedge(i,q[*ite]);
}
ss.clear();
for(int i=n;i>=1;i--){
if(i+k<=n) ss.erase(p[i+k]);
ite=(ss.insert(p[i])).first;
if(++ite!=ss.end()) addedge(i,q[*ite]);
}
for(int i=1;i<=n;i++) if(deg[i]==0) pq.push(i);
int res=0;
while(!pq.empty()){
int tp=pq.top();pq.pop();
ans[tp]=++res;
for(int j=0;j<(int)g[tp].size();j++) {
deg[g[tp][j]]--;
if(deg[g[tp][j]]==0) pq.push(g[tp][j]);
}
}
for(int i=1;i<=n;i++) printf("%d\n",ans[i]);
return 0;
}