【loj2472】IIIDX
Solution
感觉是一道很有意思的贪心题啊ovo(想了一万个假做法系列==)
比较直观的想法是,既然一个数\(i\)只会对应一个\(\lfloor\frac{i}{k}\rfloor\),那么这个不小于关系可以建成一棵树的样子,每个节点的编号代表这个节点对应的位置,然后第\(i\)号点的父亲是\(\lfloor\frac{i}{k}\rfloor\)
然后这个时候我们可以想到一个初步的贪心策略:注意到\(d\)的顺序并不影响结果,所以我们可以考虑将\(d\)从小到大排序之后,我们按照深度一层一层处理(假设这层的节点数量为\(x\)),先从\(d\)中没有分配的位置中取出前\(x\)个数,然后从大到小分配到这层的节点上
但是这是一个假做法qwq只能在\(d\)互不相同的情况下保证正确性
如果说存在相同的\(d\),那么可能会出现这种情况:
因为相同的值我们其实可以放到子树里面去,然后我们就可以用更大的值来填这层的点了
这个时候注意到,我们可能需要“预留”一些值放到子树里面去,所以我们换一个角度来思考问题:我们将\(d\)从大到小排序,考虑如果我们将某一个位置\(i\)的值分配给当前节点\(x\),那么必须保证不小于\(d_i\)的数最少要有\(sz[x]\)个(其中\(sz[x]\)表示\(x\)的子树大小),因为我们已经将\(d\)排了序,所以其实相当于要保证\(i\)以及前面的还没有被分配掉的位置最少要有\(sz[x]\)个,如果说有多个相同的数,那么从贪心的角度来看肯定是要取最靠右的那个
所以我们可以用线段树维护一个\(f\)数组,\(f[i]\)表示在\(i\)及以前的被分配掉的位置数量,那么我们按顺序给每个节点填数,每次只要找到一个\(d\)值最大的位置\(i\)满足\(i\)以前的每个位置\(j\)都满足\(j-f[j]>=sz\)即可(也就是最靠左的满足条件的那个值),然后如果说这个值有多个,把最靠右的位置分配给当前节点(记这个位置为\(x\)),并且下标为\(x\sim n\)的\(f\)值就应该对应地加上\(sz\)(因为要给当前节点的子树在前面预留位置),所以实现一个区间加和查询就好了
这里还有一个小问题,每次我们都选择当前值最靠右的位置拿来分配,那如果说这个位置之前已经被选过一次了呢?这里其实可以理解为一个比较抽象的处理:我们可以理解为把原来在这个位置的那个点以及前面的点往前移一位(如果有空位那么前面就不用再移了),把最右边的那个位置空出来给当前点(不会出现这段值相同的位置已经全被选完了还会再选到这个值情况),所以处理的时候每次直接将最右边的那个分配出去即可
最后还有一点就是,因为我们在处理每个节点的时候会为其子树预留位置,所以在处理到一个点的时候,应该把父亲预留的位置给去掉(但不能把父亲的位置也给去掉了,所以应该是减去\(sz[fa]-1\)的贡献)
代码大概长这个样子
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=5*(1e5)+10,SEG=N*4;
namespace Seg{/*{{{*/
int ch[SEG][2],tag[SEG],mn[SEG];
int n,tot;
void pushup(int x){mn[x]=min(mn[ch[x][0]]+tag[ch[x][0]],mn[ch[x][1]]+tag[ch[x][1]]);}
void _build(int x,int l,int r){
tag[x]=0;
if (l==r){mn[x]=l;return;}
int mid=l+r>>1;
ch[x][0]=++tot; _build(ch[x][0],l,mid);
ch[x][1]=++tot; _build(ch[x][1],mid+1,r);
pushup(x);
}
void build(int _n){n=_n; tot=1; _build(1,1,n);}
void _update(int x,int l,int r,int lx,int rx,int delta){
if (l<=lx&&rx<=r) {tag[x]+=delta; return;}
int mid=lx+rx>>1;
if (r<=mid) _update(ch[x][0],l,r,lx,mid,delta);
else if (l>mid) _update(ch[x][1],l,r,mid+1,rx,delta);
else{
_update(ch[x][0],l,mid,lx,mid,delta);
_update(ch[x][1],mid+1,r,mid+1,rx,delta);
}
pushup(x);
}
void update(int l,int r,int delta){_update(1,l,r,1,n,delta);}
int _query(int x,int lx,int rx,int k,int tg){
tg+=tag[x];
if (lx==rx) return (mn[x]+tg)>=k?lx:lx+1;
int mid=lx+rx>>1,rval=mn[ch[x][1]]+tg+tag[ch[x][1]];
if (k<=rval) return _query(ch[x][0],lx,mid,k,tg);
else return _query(ch[x][1],mid+1,rx,k,tg);
}
int query(int k){return _query(1,1,n,k,0);}
}/*}}}*/
int d[N],ans[N],ed[N],fa[N],sz[N];
int clean[N];
int n;
double K;
bool cmp(int x,int y){return x>y;}
void prework(){
for (int i=n;i>=1;--i){
fa[i]=(int)(1.0*i/K);
ed[i]=i; ++sz[i];
sz[fa[i]]+=sz[i];
if (i!=n&&d[i]==d[i+1]) ed[i]=ed[i+1];
}
}
int main(){
#ifndef ONLINE_JUDGE
freopen("a.in","r",stdin);
#endif
scanf("%d%lf",&n,&K);
for (int i=1;i<=n;++i) scanf("%d",d+i);
sort(d+1,d+1+n,cmp);
Seg::build(n);
prework();
for (int i=1;i<=n;++i){
if (fa[i]&&!clean[fa[i]]){
Seg::update(ans[fa[i]],n,sz[fa[i]]-1);
clean[fa[i]]=true;
}
ans[i]=Seg::query(sz[i]);
ans[i]=ed[ans[i]];
Seg::update(ans[i],n,-sz[i]);
}
for (int i=1;i<=n;++i) printf("%d ",d[ans[i]]);
printf("\n");
}