转化一下问题变成给定一棵树,一个序列,求父亲的权值小于子树的最大方案。
直接贪心会在有重复权值时出现错误,我们考虑用线段树优化贪心。
将序列从小到大排序,线段树上每个点记录他和他右边当前还可用的权值,注意这里我们并不一定维护的都是正确的,但是我们要保证我们需要用到的限制一定都体现了出来,比如 1 2 5 5 5 5 5 6 7 8 9 K=2,我们放第二个点时,会放到3这个位置,于是我们将1~3区间减7,这时候4~11的权值我们并没有修改,但是需要用到的限制只有前三个点,所以是正确的,之后我们每次在线段树上找到最大的可用位置,然后放到和他权值相同的最左边就可以了。
还要注意我们放一个点时需要先将其父亲的限制去掉。
#include <cstdio> #include <algorithm> #include <cmath> #include <map> #define N 500500 using namespace std; int n,a[N],fa[N],size[N],pp[N]; double K; int lazy[N<<2],minn[N<<2]; void pushdown(int rt){ if(lazy[rt]){ lazy[rt<<1]+=lazy[rt];minn[rt<<1]+=lazy[rt]; lazy[rt<<1|1]+=lazy[rt];minn[rt<<1|1]+=lazy[rt]; lazy[rt]=0; } } void build(int rt,int l,int r){ if(l==r){minn[rt]=n-l+1;return;} int mid=(l+r)>>1; build(rt<<1,l,mid); build(rt<<1|1,mid+1,r); minn[rt]=min(minn[rt<<1],minn[rt<<1|1]); } void update(int rt,int l,int r,int x,int y,int z){ if(x<=l&&r<=y){ lazy[rt]+=z;minn[rt]+=z; return ; } pushdown(rt); int mid=(l+r)>>1; if(x<=mid)update(rt<<1,l,mid,x,y,z); if(y>mid)update(rt<<1|1,mid+1,r,x,y,z); minn[rt]=min(minn[rt<<1],minn[rt<<1|1]); } int query(int rt,int l,int r,int x){ if(l==r){ if(minn[rt]>=x)return l; return l-1; } pushdown(rt); int mid=(l+r)>>1; if(minn[rt<<1]>=x)return query(rt<<1|1,mid+1,r,x); else return query(rt<<1,l,mid,x); } map<int,int> L; int main(){ scanf("%d%lf",&n,&K); for(int i=1;i<=n;i++)scanf("%d",&a[i]); sort(a+1,a+n+1); for(int i=1;i<=n;i++) if(a[i]!=a[i-1])L[a[i]]=i; for(int i=1;i<=n;i++)fa[i]=floor(i/K); for(int i=n;i;i--){ size[i]++; size[fa[i]]+=size[i]; } build(1,1,n); for(int i=1;i<=n;){ if(fa[i])update(1,1,n,1,pp[fa[i]],size[fa[i]]-1); do{ int x=query(1,1,n,size[i]); pp[i]=L[a[x]]++; update(1,1,n,1,pp[i],-size[i]); i++; }while(i<=n&&fa[i]==fa[i-1]); } for(int i=1;i<=n;i++)printf("%d%c",a[pp[i]],((i==n)?' ':' ')); return 0; }