【BZOJ5249】【九省联考2018】—IIIDX(线段树)

传送门

题意:给你一座森林和一堆权值,要把每个权值分配给点,要保证儿子不小于父亲的权值,问字典序最大的方案

开始第一眼以为是傻逼题,结果交上去果断挂掉
因为在有权值相同的情况,比如说 n = 4 , k = 2 , d = 1 , 1 , 1 , 2 n=4,k= 2,d=1,1,1,2 ,答案是 1 , 1 , 1 , 2 1,1,1,2 ,实际上则是 1 , 1 , 2 , 1 1,1,2,1

发现每个要要在保证自己最大的情况下是需要给后面的子树“预留”一些节点的,而且自己也要满足别的点的预留情况
可以用线段树维护一下第 i i 大的数能给更小的数预留多少
这样分配的时候可以在线段树上二分找到最大的满足的点

记得加了一个点后要把它父亲预留的影响去掉

#include<bits/stdc++.h>
using namespace std;
inline int read(){
	char ch=getchar();
	int res=0,f=1;
	while(!isdigit(ch)){if(ch=='-')f=-f;ch=getchar();}
	while(isdigit(ch))res=(res+(res<<2)<<1)+(ch^48),ch=getchar();
	return res*f;
}
const int N=500005;
int tr[N<<2],tag[N<<2];
int cnt[N],a[N],pos[N],fa[N],siz[N],n;
double k;
#define lc (u<<1)
#define rc ((u<<1)|1)
#define mid ((l+r)>>1)
inline void pushup(int u){
	tr[u]=min(tr[lc],tr[rc]);
}
inline void pushdown(int u){
	tr[lc]+=tag[u],tr[rc]+=tag[u],tag[lc]+=tag[u],tag[rc]+=tag[u],tag[u]=0;
}
void buildtree(int u,int l,int r){
	if(l==r){tr[u]=l;return;}
	buildtree(lc,l,mid);
	buildtree(rc,mid+1,r);
	pushup(u);
}
void update(int u,int l,int r,int st,int des,int p){
	if(st<=l&&r<=des){
		tr[u]+=p,tag[u]+=p;return;
	}
	pushdown(u);
	if(st<=mid)update(lc,l,mid,st,des,p);
	if(mid<des)update(rc,mid+1,r,st,des,p);
	pushup(u);
}
int query(int u,int l,int r,int p){
	if(l==r)return tr[u]>=p?l:l+1;
	pushdown(u);
	if(p<=tr[rc])return query(lc,l,mid,p);
	else return query(rc,mid+1,r,p);
}
inline bool comp(int a,int b){
	return a>b;
}
int main(){
	n=read(),scanf("%lf",&k);
	for(int i=1;i<=n;i++)siz[i]=1,a[i]=read();
	sort(a+1,a+n+1,comp);
	for(int i=n;i;i--)
		fa[i]=(int)floor((double)i/k),siz[fa[i]]+=siz[i];
	cnt[n]=n;
	for(int i=n-1;i;i--)
		if(a[i]==a[i+1])cnt[i]=cnt[i+1];else cnt[i]=i;
	buildtree(1,1,n);
	for(int i=1;i<=n;i++){
		if(fa[i]&&fa[i]!=fa[i-1])update(1,1,n,pos[fa[i]],n,siz[fa[i]]-1);
		int p=cnt[query(1,1,n,siz[i])];
		cnt[p]++,pos[i]=p;
		update(1,1,n,p,n,-siz[i]);
	}
	for(int i=1;i<=n;i++)cout<<a[pos[i]]<<" ";
}

猜你喜欢

转载自blog.csdn.net/qq_42555009/article/details/87928315