[九省联考2018]IIIDX 题解

题目链接 : https://www.luogu.com.cn/problem/P4364

题意:

一棵森林 \(N\) 个点,一个常数 \(K\) ,第 \(i\) 号点的父亲是 \(\lfloor\frac{i}{K}\rfloor\) ,有 \(N\) 个权值 \(A_i\) ,要求每个结点对应一个权值,且父亲的权值小于等于儿子的权值。要求字典序最大。 \((1\le N\le5\times10^5,1<K\le10^9,1\le A_i\le 10^9)\)

题解:

这道题实现起来挺容易,但是在思路上细节很多……

首先说一下,父亲是 \(\lfloor\frac{i}{K}\rfloor\) 这个性质其实没什么用,只是写起来会好一点。

容易想到一个假算法:将权值从大到小排序,把长度为子树大小的一段按子树编号从小到大丢给它们,递归下去得到答案。

不过有重复的数时,这就假了:有一个儿子的权值比他父亲大,他父亲的兄弟权值和它父亲一样,替换他儿子和兄弟的权值会更优。

可以从编号 \(1\)\(N\) 扫描一遍,一个一个求出能取的最大值,由于字典序的性质,显然是对的。

考虑先把这些权值从大到小排序,每次找到第一个能刚好取出该子树大小的权值。注意,不是位置。

具体该去哪个位置呢?为了实现方便,显然要一个子树内的权值都在这个树根的权值的左边,可以假定是取最右边的,毕竟有些相同的权值要分给自己的儿孙。

还要注意,每次进入到下一层的时候,要将自己父亲的占位去掉。

考虑如何实现找到这个权值,这显然是满足二分单调性的,但直接二分很难做,做出来也会多个 \(\log\) ,不如在线段树上二分。

看一下有哪些操作:在一段区间内添上若干个个。在一段区间内去掉若干个(和上一个一样)。找到最左边有若干个空位的。

在线段树上不能二分到哪些被钦定了但是没实际赋值的区间,因此可以考虑找到一段后缀空位都大于等于那个值的,这样能保证中间没有被钦定的。

因此变成了区间加减,维护最小值(都大于),线段树上二分。这个二分有可能最后找到的地方还是大于等于那个值的,需要判一下。

另外再考虑如何找到重复的数中没被取的最右边的一个:首先先找到最右边的那个位置,接着再记一下往左移几格,毕竟都是一格一格取的,这样弄没有重复的值也无所谓。

时间复杂度: \(O(N\log N)\) 。代码其实看得很清楚。

代码:

#include <bits/stdc++.h>
using namespace std;
const int N=5e5+5;
int n,a[N],sz[N],fa[N],nxt[N],ans[N],tr[N<<2],tag[N<<2];
double k;
#define lc pos<<1
#define rc pos<<1|1
inline void pushup(int pos) {tr[pos]=min(tr[lc],tr[rc]);}
inline void pushdown(int pos) {int&d=tag[pos];tr[lc]+=d;tr[rc]+=d;tag[lc]+=d;tag[rc]+=d;d=0;}
void update(int l,int r,int pos,int L,int R,int x){
	if(L<=l&&r<=R) {tr[pos]+=x;tag[pos]+=x;return;}pushdown(pos);int mid=(l+r)>>1;
	if(L<=mid) update(l,mid,lc,L,R,x);if(R>mid) update(mid+1,r,rc,L,R,x);pushup(pos);
}
int query(int l,int r,int pos,int x){
	if(l==r) return tr[pos]<x?l+1:l;pushdown(pos);int mid=(l+r)>>1;
	return tr[rc]>=x?query(l,mid,lc,x):query(mid+1,r,rc,x);
}
signed main(){
	scanf("%d%lf",&n,&k);fill_n(sz+1,n,1);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	sort(a+1,a+n+1);reverse(a+1,a+n+1);
	for(int i=n;i;i--)
		if(a[i]==a[i+1]) nxt[i]=nxt[i+1]+1;
	for(int i=n;i;i--) sz[fa[i]=i/k]+=sz[i];
	for(int i=1;i<=n;i++) update(1,n,1,i,i,i);
	for(int i=1,x;i<=n;i++){
		if(fa[i]!=fa[i-1]) update(1,n,1,ans[fa[i]],n,sz[fa[i]]-1);
		x=query(1,n,1,sz[i]);x+=nxt[x];x-=nxt[x]++;ans[i]=x;update(1,n,1,x,n,-sz[i]);
	}
	for(int i=1;i<=n;i++) printf("%d ",a[ans[i]]);
	return 0;
}
posted @ 2020-11-24 23:41  shrtcl  阅读(81)  评论(0编辑  收藏  举报