神仙题啊。
题面
https://www.luogu.org/problem/AT1984
题解
首先对序列求逆,变成相邻两个如果差大于等于$k$则可交换。
如果小于$k$,那相对位置永远不会发生变化,连一条有向边。
原序列字典序最小,就是逆序列的反序列字典序最大(见“菜肴制作”)
建反边,用大根堆(普通的$priority queue<int>$)拓扑排序,再反过来。
都是这样边数是$O(n^2)$的,考虑优化,每个点只向值域内最近的第一个大于自己的和第一个小于自己的连边,用一个扫描线$+ set$实现。
#include<set> #include<queue> #include<vector> #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #define N 500500 #define ri register int using namespace std; int n,k; int a[N],p[N],ans[N],ans2[N],ru[N]; set<int> s; vector<int> to[N]; priority_queue<int> pq; void add_edge(int u,int v) { to[v].push_back(u); ru[u]++; } inline int read() { int ret=0,f=0; char ch=getchar(); while (ch<'0' || ch>'9') f|=(ch=='-'),ch=getchar(); while (ch>='0' && ch<='9') ret*=10,ret+=(ch-'0'),ch=getchar(); return f?-ret:ret; } int main() { n=read(); k=read(); for (ri i=1;i<=n;i++) a[p[i]=read()]=i; for (ri i=1;i<=n+k-1;i++) { if (i<=n) s.insert(p[i]); if (i-k+1>=1) s.erase(p[i-k+1]); if (i+1<=n) { set<int> :: iterator cur=s.upper_bound(p[i+1]); if (cur!=s.end()) add_edge(i+1,a[*cur]); } if (i-k+1>=1) { set<int> :: iterator cur=s.upper_bound(p[i-k+1]); if (cur!=s.end()) add_edge(i-k+1,a[*cur]); } } for (ri i=1;i<=n;i++) if (!ru[i]) pq.push(i); int cc=0; while (!pq.empty()) { int x=pq.top(); pq.pop(); ans[++cc]=x; for (ri i=0;i<to[x].size();i++) { int y=to[x][i]; ru[y]--; if (!ru[y]) pq.push(y); } } for (ri i=1;i<=n;i++) ans2[ans[n-i+1]]=i; for (ri i=1;i<=n;i++) printf("%d ",ans2[i]); return 0; }