1. 程式人生 > 實用技巧 >九省聯考2018 IIIDX

九省聯考2018 IIIDX

IIIDX

Osu 聽過沒?那是 Konano 最喜歡的一款音樂遊戲,而他的夢想就是有一天自己也能做個獨特酷炫的音樂遊戲。現在,他在世界知名遊戲公司 KONMAI 內工作,離他的夢想也越來越近了。

這款音樂遊戲內一般都包含了許多歌曲,歌曲越多,玩家越不易玩膩。同時,為了使玩家在遊戲上氪更多的金錢花更多的時間,遊戲一開始一般都不會將所有曲目公開,有些曲目你需要通關某首特定歌曲才會解鎖,而且越晚解鎖的曲目難度越高。

這一天,Konano 接到了一個任務,他需要給正在製作中的遊戲《IIIDX》安排曲目的解鎖順序。遊戲內共有 \(n\) 首曲目,每首曲目都會有一個難度 \(d\),遊戲內第 \(i\)

首曲目會在玩家 Pass 第 \(\left\lfloor \frac i k \right\rfloor\) 首曲目後解鎖(\(\left\lfloor x \right\rfloor\) 為下取整符號)若 \(\left\lfloor \frac i k \right\rfloor = 0\),則說明這首曲目無需解鎖

舉個例子:當 \(k = 2\) 時,第 \(1\) 首曲目是無需解鎖的(\(\left\lfloor \frac 12 \right\rfloor = 0\)),第 \(7\) 首曲目需要玩家 Pass 第 \(\left\lfloor \frac 72 \right\rfloor = 3\)

首曲目才會被解鎖。

Konano 的工作,便是安排這些曲目的順序,使得每次解鎖出的曲子的難度不低於作為條件需要玩家通關的曲子的難度,即使得確定順序後的曲目的難度對於每個 \(i\) 滿足 \(d_i \geq d_{\left\lfloor \frac ik \right\rfloor}\)

當然這難不倒曾經在資訊學競賽摸魚許久的 Konano。那假如是你,你會怎麼解決這份任務呢?

\(1\leq n\leq 500000\)

題解

精髓的貪心。

首先這個限制關係是個樹形結構,並且我們要最小化先序遍歷的字典序。

把數值從大到小排序,對每個\(i\)維護一個\(f_i\),表示\(\geq i\)的還有幾個數可以選。

因為要使字典序最小,所以我們對位置從左到右貪心安排。發現我們需要給當前位置\(x\)安排一個儘可能大的數值\(i\),滿足\(\forall j\leq i,f_j\geq \text{siz}_x\)。這是因為子樹內要放\(\text{siz}_x\)\(\geq i\)的數。

但是從左到右的順序不能保證子樹接著被安排,所以如果我們什麼都不幹就繼續往後貪心的話可能會導致\(\geq i\)的數不足\(\text{siz}_x\)個。解決方法是把\(j\leq i\)\(f_j\)都減去\(\text{siz}_x\),表示給子樹預留位置。這樣繼續往右貪心時就不會出現這種無解的情況。

時間複雜度\(O(n\log 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," \n"[i==n]);
	return 0;
}