5249: [2018多省省队联测]IIIDX
Description
【题目背景】
Osu听过没?那是Konano最喜欢的一款音乐游戏,而他的梦想就是有一天自己也能做个独特酷炫的音乐游戏。现在,他在世界知名游戏公司KONMAI内工作,离他的梦想也越来越近了。这款音乐游戏内一般都包含了许多歌曲,歌曲越多,玩家越不易玩腻。同时,为了使玩家在游戏上氪更多的金钱花更多的时间,游戏一开始一般都不会将所有曲目公开,有些曲目你需要通关某首特定歌曲才会解锁,而且越晚解锁的曲目难度越高。
【题目描述】
这一天,Konano接到了一个任务,他需要给正在制作中的游戏《IIIDX》安排曲目的解锁顺序。游戏内共有n首曲目,每首曲目都会有一个难度d,游戏内第i首曲目会在玩家Pass第trunc(i/k)首曲目后解锁(x为下取整符号)若trunc(i/k)=0,则说明这首曲目无需解锁。举个例子:当k=2时,第1首曲目是无需解锁的(trunc(1/2)=0),第7首曲目需要玩家Pass第trunc(7/2)=3首曲目才会被解锁。Konano的工作,便是安排这些曲目的顺序,使得每次解锁出的曲子的难度不低于作为条件需要玩家通关的曲子的难度,即使得确定顺序后的曲目的难度对于每个i满足Di≥Dtrunc(i/k)。当然这难不倒曾经在信息学竞赛摸鱼许久的Konano。那假如是你,你会怎么解决这份任务呢?
Input
第1行1个正整数n和1个小数k,n表示曲目数量,k其含义如题所示。
第2行n个用空格隔开的正整数d,表示这n首曲目的难度。
1 ≤ n ≤ 500000
1 < k ≤ 10^9
1 < d ≤ 10^9
Output
输出1行n个整数,按顺序输出安排完曲目顺序后第i首曲目的难度。
若有多解,则输出d1最大的;若仍有多解,则输出d2最大的,以此类推。
Sample Input
4 2.0
114 514 1919 810
Sample Output
114 810 514 1919
题解
我们可以把限制关系抽象成森林,第\(i\)个结点的父亲是第\(\left \lfloor \frac{i}{k} \right \rfloor\)个结点。如果新建一个根\(0\)号节点,并从\(0\)向所有树的根连边,就会形成一棵树。这样题目就变成了把数安放到一个树上使得儿子比父亲大,同时解的字典序还要最大。
考虑贪心,将数从大到小排序,按照编号顺序依次确定每个节点所填的数。为了保证儿子比父亲填的数大,在填某个节点\(i\)的数时,假设其子树大小为\(siz[i]\),我们要给它的子树预定至少\(siz[i]\)个数。设\(f[i]\)表示第\(i\)个节点左边已经被预定的数的数量,那么\(i-f[i]\)即为第\(i\)个节点左边可用的数的数量。由于要为其子树预定至少\(siz[i]\)个数,我们需要找到一个位置\(x\),使得\(x\)右侧每个位置可用的数的数量都不小于\(siz[i]\),并把位置\(x\)上的数放到节点\(i\)上。如果位置\(x\)上的数不只出现一次,而是连续的一段,我们把最右边的那个放到节点\(i\)上。最后要把在选择的位置之后的所有位置的分\(f\)都加上\(siz[i]-1\)。
举个例子:9 8 7 6 5 5 5 5 5 4 3 2
假如第\(1\)个结点的子树大小为\(7\),那么第\(1\)个结点的值即为第\(7\)大的数:\(5\)
我们把最右边的\(5\)(第\(9\)位)给第\(1\)个结点
然后在 \([1,8]\) 这个区间内预定\(6\)个数给第\(1\)个结点的子树,于是\(F_9\cdots F_n\)都加上\(6\)
另外,在处理节点\(i\)时,如果它有父节点且父节点的预定值还没有撤销,要先撤销父节点预定的值。
用线段树去维护这一过程即可。
代码
#include<bits/stdc++.h>
#define MAXN 500010
#define lc rt<<1
#define rc rt<<1|1
using namespace std;
int fa[MAXN],t[MAXN<<2],tag[MAXN<<2],N,a[MAXN],siz[MAXN],num[MAXN],ans[MAXN];
double K;
bool vis[MAXN];
inline bool cmp(int a,int b){return a>b;}
inline void Up(int rt){t[rt]=min(t[lc],t[rc]);}
inline void Down(int rt){
if(tag[rt]){
t[lc]+=tag[rt];tag[lc]+=tag[rt];
t[rc]+=tag[rt];tag[rc]+=tag[rt];
tag[rt]=0;
}
}
void Build(int l,int r,int rt){
if(l==r){t[rt]=l;return;}
int mid=l+r>>1;
Build(l,mid,lc);Build(mid+1,r,rc);
Up(rt);
}
void Add(int L,int R,int l,int r,int rt,int v){
if(L<=l&&R>=r){t[rt]+=v;tag[rt]+=v;return;}
int mid=l+r>>1;
Down(rt);
if(L<=mid)Add(L,R,l,mid,lc,v);
if(R>mid)Add(L,R,mid+1,r,rc,v);
Up(rt);
}
int Query(int l,int r,int rt,int v){
if(l==r)return t[rt]>=v? l:l+1;
int mid=l+r>>1;
Down(rt);
if(t[rc]>=v)return Query(l,mid,lc,v);
else return Query(mid+1,r,rc,v);
}
int main(){
#ifndef ONLINE_JUDGE
freopen("iiidx.in","r",stdin);
freopen("iiidx.out","w",stdout);
#endif
scanf("%d%lf",&N,&K);
for(int i=1;i<=N;i++)scanf("%d",&a[i]),siz[i]=1;
sort(a+1,a+1+N,cmp);
for(int i=N-1;i>=1;i--)num[i]=a[i]==a[i+1]? num[i+1]+1:0;
for(int i=1;i<=N;i++)fa[i]=floor(i/K);
for(int i=N;i>=1;i--)siz[fa[i]]+=siz[i];
Build(1,N,1);
for(int i=1;i<=N;i++){
if(fa[i]&&!vis[fa[i]]){Add(ans[fa[i]],N,1,N,1,siz[fa[i]]-1);vis[fa[i]]=1;}
int x=Query(1,N,1,siz[i]);
x=x+num[x];num[x]++;x-=(num[x]-1);ans[i]=x;
Add(ans[i],N,1,N,1,-siz[i]);
}
for(int i=1;i<=N;i++)printf("%d%c",a[ans[i]],i==N?'\n':' ');
return 0;
}