题解 [AGC001F] Wide Swap
先说自己口胡的垃圾做法:
肯定是想按 \([1, n]\) 的顺序将每个数放到能放到的最小位置上
那么根据 \(i\) 和 \(i+1\) 能否交换可以将 \([1, n]\) 这个序列分为若干个连续段
那么每个数能放到的最小位置就是所在连续段每个数所在下标的最小值
那么贪心放,放完一个数后将这个数和目标位置交换
具体地,若最小值在 \([1, n]\) 中 \(i\) 处取到,则将 \(i-1\) 与 \(i\) 交换,将 \(i-2\) 与 \(i-1\) 交换,……
然后将编号 1 删除,将 1 所在位置删除,判断新的 \(i\) 是否能与 \(i+1\) 交换以维护连续段
应该可以做到 \(O(n\log n)\)
然后正解:其实差不多吧
考虑原序列 \(P\) 的逆置换 \(Q\)(就是上面那个 \([1, n]\) 的序列)
那么操作就是若 \(|Q_i-Q_{i+1}|<K\) 则可以交换 \(i\) 和 \(i+1\)
那么就容易发现关键性质了:差值 \(<K\) 的两个数在操作后顺序不变
放到序列 \(P\) 上,限制就是若 \(|i-j|<K\),则操作后 \(P_i\) 和 \(P_j\) 的大小关系不变
那么现在限制就是 \(O(nk)\) 个形如 \(P_i<P_j\) 的限制了
- 关于找到一个字典序最小的排列,有若干形如 \(P_i<P_j\) 的限制:
那么问题就相当于要给 \(n\) 个点钦定一个访问顺序,限制是一些形如 \(i\) 比 \(j\) 先访问的限制
那么 \(P_i\) 就是这个点是第几个被访问到的
一个可能的错误做法是贪心取编号最小的入度为 0 的点,错因会了正确做法就显然了
正确做法是按顺序最小化每个 \(P_i\)
那么 \(P_1\) 的最小值是能到达点 1 的点的个数,\(2\cdots n\) 同理
这个个数怎么算呢?考虑将原图的所有边反向,跑拓扑排序时贪心选编号最大的点先跑
那么跑到点 \(i\) 时图中剩下的点就都是 \(i\) 的后继了,此时 \(i\) 的后继个数(即原图中能到达 \(i\) 的点的个数)是容易计算的
复杂度 \(O(n\log n)\)
点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
#define N 500010
#define fir first
#define sec second
#define ll long long
//#define int long long
char buf[1<<21], *p1=buf, *p2=buf;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf, 1, 1<<21, stdin)), p1==p2?EOF:*p1++)
inline int read() {
int ans=0, f=1; char c=getchar();
while (!isdigit(c)) {if (c=='-') f=-f; c=getchar();}
while (isdigit(c)) {ans=(ans<<3)+(ans<<1)+(c^48); c=getchar();}
return ans*f;
}
int n, k;
int p[N], ans[N];
#define tl(p) tl[p]
#define tr(p) tr[p]
priority_queue<int> q;
int tl[N<<2], tr[N<<2], maxn[N<<2], maxi[N<<2];
inline void pushup(int p) {
maxn[p]=max(maxn[p<<1], maxn[p<<1|1]);
maxi[p]=maxn[p<<1]==maxn[p]?maxi[p<<1]:maxi[p<<1|1];
}
void build(int p, int l, int r) {
tl(p)=l; tr(p)=r;
if (l==r) {maxn[p]=::p[l]; maxi[p]=l; return ;}
int mid=(l+r)>>1;
build(p<<1, l, mid);
build(p<<1|1, mid+1, r);
pushup(p);
}
void del(int p, int pos) {
if (tl(p)==tr(p)) {maxn[p]=-INF; return; }
int mid=(tl(p)+tr(p))>>1;
if (pos<=mid) del(p<<1, pos);
else del(p<<1|1, pos);
pushup(p);
}
pair<int, int> query(int p, int l, int r) {
if (l<=tl(p)&&r>=tr(p)) return {maxn[p], maxi[p]};
int mid=(tl(p)+tr(p))>>1;
if (l<=mid&&r>mid) return max(query(p<<1, l, r), query(p<<1|1, l, r));
else if (l<=mid) return query(p<<1, l, r);
else return query(p<<1|1, l, r);
}
signed main()
{
n=read(); k=read();
for (int i=1; i<=n; ++i) p[i]=read();
build(1, 1, n);
for (int i=1; i<=n; ++i) if (query(1, max(1, i-k+1), min(n, i+k-1)).sec==i) q.push(i);
pair<int, int> t;
for (int i=n; i; --i) {
int pos=q.top(); q.pop();
del(1, pos); ans[pos]=i;
t=query(1, max(1, pos-k+1), pos);
if (t.fir>0 && query(1, max(1, t.sec-k+1), min(n, t.sec+k-1)).sec==t.sec) q.push(t.sec);
t=query(1, pos, min(n, pos+k-1));
if (t.fir>0 && query(1, max(1, t.sec-k+1), min(n, t.sec+k-1)).sec==t.sec) q.push(t.sec);
}
for (int i=1; i<=n; ++i) printf("%d\n", ans[i]);
return 0;
}