[九省联考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;
}