IIIDX
Osu 听过没?那是 Konano 最喜欢的一款音乐游戏,而他的梦想就是有一天自己也能做个独特酷炫的音乐游戏。现在,他在世界知名游戏公司 KONMAI 内工作,离他的梦想也越来越近了。
这款音乐游戏内一般都包含了许多歌曲,歌曲越多,玩家越不易玩腻。同时,为了使玩家在游戏上氪更多的金钱花更多的时间,游戏一开始一般都不会将所有曲目公开,有些曲目你需要通关某首特定歌曲才会解锁,而且越晚解锁的曲目难度越高。
这一天,Konano 接到了一个任务,他需要给正在制作中的游戏《IIIDX》安排曲目的解锁顺序。游戏内共有 (n) 首曲目,每首曲目都会有一个难度 (d),游戏内第 (i) 首曲目会在玩家 Pass 第 (leftlfloor frac i k ight floor) 首曲目后解锁((leftlfloor x ight floor) 为下取整符号)若 (leftlfloor frac i k ight floor = 0),则说明这首曲目无需解锁。
举个例子:当 (k = 2) 时,第 (1) 首曲目是无需解锁的((leftlfloor frac 12 ight floor = 0)),第 (7) 首曲目需要玩家 Pass 第 (leftlfloor frac 72 ight floor = 3) 首曲目才会被解锁。
Konano 的工作,便是安排这些曲目的顺序,使得每次解锁出的曲子的难度不低于作为条件需要玩家通关的曲子的难度,即使得确定顺序后的曲目的难度对于每个 (i) 满足 (d_i geq d_{leftlfloor frac ik ight floor})。
当然这难不倒曾经在信息学竞赛摸鱼许久的 Konano。那假如是你,你会怎么解决这份任务呢?
(1leq nleq 500000)
题解
精髓的贪心。
首先这个限制关系是个树形结构,并且我们要最小化先序遍历的字典序。
把数值从大到小排序,对每个(i)维护一个(f_i),表示(geq i)的还有几个数可以选。
因为要使字典序最小,所以我们对位置从左到右贪心安排。发现我们需要给当前位置(x)安排一个尽可能大的数值(i),满足(forall jleq i,f_jgeq ext{siz}_x)。这是因为子树内要放( ext{siz}_x)个(geq i)的数。
但是从左到右的顺序不能保证子树接着被安排,所以如果我们什么都不干就继续往后贪心的话可能会导致(geq i)的数不足( ext{siz}_x)个。解决方法是把(jleq i)的(f_j)都减去( ext{siz}_x),表示给子树预留位置。这样继续往右贪心时就不会出现这种无解的情况。
时间复杂度(O(nlog n))。
CO int N=5e5+10;
int a[N];
pair<int,int> b[N];
int tree[4*N],tag[4*N];
#define lc (x<<1)
#define rc (x<<1|1)
#define mid ((l+r)>>1)
void build(int x,int l,int r){
if(l==r) {tree[x]=b[l].second; return;}
build(lc,l,mid),build(rc,mid+1,r);
tree[x]=min(tree[lc],tree[rc]);
}
IN void push_down(int x){
if(tag[x]){
tree[lc]+=tag[x],tag[lc]+=tag[x];
tree[rc]+=tag[x],tag[rc]+=tag[x];
tag[x]=0;
}
}
void modify(int x,int l,int r,int ql,int qr,int v){
if(ql<=l and r<=qr) {tree[x]+=v,tag[x]+=v; return;}
push_down(x);
if(ql<=mid) modify(lc,l,mid,ql,qr,v);
if(qr>mid) modify(rc,mid+1,r,ql,qr,v);
tree[x]=min(tree[lc],tree[rc]);
}
int query(int x,int l,int r,int v){
if(l==r) return tree[x]>=v?l:l+1;
push_down(x);
if(tree[rc]>=v) return query(lc,l,mid,v);
else return query(rc,mid+1,r,v);
}
#undef lc
#undef rc
#undef mid
int fa[N],siz[N],pos[N];
int main(){
int n=read<int>();
double k;scanf("%lf",&k);
for(int i=1;i<=n;++i) read(a[i]);
sort(a+1,a+n+1,greater<int>());
int m=0;
for(int l=1,r;l<=n;l=r+1){
for(r=l;r+1<=n and a[r+1]==a[l];++r);
b[++m]={a[l],r};
}
build(1,1,m);
for(int i=n;i>=1;--i){
fa[i]=floor(i/k); // edit 1: floor
++siz[i],siz[fa[i]]+=siz[i];
}
for(int i=1;i<=n;++i){
if(fa[i] and fa[i]!=fa[i-1]) modify(1,1,m,pos[fa[i]],m,siz[fa[i]]-1);
pos[i]=query(1,1,m,siz[i]);
modify(1,1,m,pos[i],m,-siz[i]);
}
for(int i=1;i<=n;++i) write(b[pos[i]].first,"
"[i==n]);
return 0;
}