[九省联考2018] IIIDX 线段树+贪心
题目:
给出 k 和 n 个数,构造一个序列使得 d[i]>=d[i/k] ,并且字典序最大。
分析:
听说,当年省选的时候,这道题挡住了大批的高手,看上去十分简单,实际上那道弯段时间内是转不过来的。
首先,一个套路是,将这个序列的关系抽象成一棵树,i的父亲是floor(i/k),我们要要求子树内部的点的权值都比父亲大。
我们观察子任务的特殊限制,di不一样?
我们想,把原序列从大到小排序,在树上dfs给点赋值,在给一个点赋值时,要在序列上预留出siz[这棵子树]的位置,用来给子树内部的点赋值(排序就是为了方便在序列上预留位置)。
但是,如果有重复的元素就办不了了,比如n=4, k=2 序列是1,1,1,2.
排好应该是1 1 2 1,但是我们上面的方法会排成1 1 1 2
为什么呢?
我们从大到小排序,应该得到2 1 1 1.我们递归,首先是树根节点,需要预留4个位置,所以肯定要选最后一个(1),然后递归到第一个子树(序号为2),size为2,需要在这个子树留两个位置,所以我们把第二个1给了序号2这个位置,然后那个2就给了它的儿子(序号4)但是实际上,我们可以给它留一个1,把这个4给位置3.
我这么分析会很乱,但是我们于情于理考虑一下,我们解决完了位置2,若想字典序最大,我们应该先最大化3这个位置。
所以我们应该在给序号2找到最大值之后,应该为他的子树预留一些值,使这些值在合法的基础上尽可能小。
所以我们应该排序后,建立一个序列c,c[i]的值代表排好序的序列上,i及i左边的所有值还剩下多少个可以选的。
(序列c一开始肯定是1,2,3,4,5……)
然后我们每次为一个点赋值,应该找到最靠左的一个位置i,使所有j>=i满足c[j]>=siz[这个点的子树]。
并且选完之后,要找到这个位置往右最右边和他值相等的那个位置(前提是没有被用过的)。
然后,用区间减法计算贡献,之后接着处理这个点的兄弟节点,没有了兄弟节点之后在进入某个点的子节点。
进入一个子节点时,要将之前预留的位置(区间减去的数值)加回来,但是已经分配出去的(父亲的)权值就不要加回来了。
所以是siz-1
代码:
1 #include<bits/stdc++.h> 2 #define db double 3 using namespace std; 4 const int N=500005,inf=1e9; 5 struct segtree{ 6 int l,r,ls,rs,mn,lz; 7 }t[N*4];int a[N],b[N],ans[N],m,rt; 8 db k;int n,siz[N],fa[N],cnt[N],o=0; 9 bool cmp(int u,int v){return u>v;} 10 void pushup(int cur){ 11 int ls=t[cur].ls,rs=t[cur].rs; 12 t[cur].l=t[ls].l;t[cur].r=t[rs].r; 13 t[cur].mn=min(t[ls].mn,t[rs].mn); 14 } void pushdown(int cur){ 15 int ls=t[cur].ls,rs=t[cur].rs,d=t[cur].lz; 16 t[ls].mn+=d;t[ls].lz+=d; 17 t[rs].mn+=d;t[rs].lz+=d; 18 t[cur].lz=0;return ; 19 } void build(int x,int l,int r){ 20 if(l==r){ 21 t[x].l=t[x].r=l;t[x].ls=t[x].rs=-1; 22 t[x].mn=l;return ; 23 } int mid=l+r>>1; 24 t[x].ls=o++;t[x].rs=o++; 25 build(t[x].ls,l,mid);build(t[x].rs,mid+1,r); 26 pushup(x);return ; 27 } void update(int x,int l,int r,int c){ 28 if(l<=t[x].l&&t[x].r<=r) 29 {t[x].mn+=c;t[x].lz+=c;return ;} 30 pushdown(x);int mid=t[x].l+t[x].r>>1; 31 if(l<=mid) update(t[x].ls,l,r,c); 32 if(mid<r) update(t[x].rs,l,r,c); 33 pushup(x);return ; 34 } int query(int x,int p){ 35 if(t[x].l==t[x].r) 36 return t[x].mn>=p?t[x].l:t[x].l+1; 37 pushdown(x);int mid=t[x].l+t[x].r>>1; 38 if(p<=t[t[x].rs].mn) return query(t[x].ls,p); 39 else return query(t[x].rs,p);return 0; 40 } int main(){ 41 scanf("%d%lf",&n,&k);rt=o++; 42 for(int i=1;i<=n;i++) scanf("%d",&a[i]); 43 sort(a+1,a+1+n,cmp);build(rt,1,n); 44 for(int i=n-1;i;i--) 45 if(a[i]==a[i+1]) cnt[i]=cnt[i+1]+1; 46 else cnt[i]=0; 47 for(int i=1;i<=n;i++) 48 fa[i]=(int)floor(i/k),siz[i]=1; 49 for(int i=n;i;i--) siz[fa[i]]+=siz[i]; 50 for(int i=1;i<=n;i++){ 51 if(fa[i]&&fa[i]!=fa[i-1]) 52 update(rt,ans[fa[i]],n,siz[fa[i]]-1); 53 int x=query(rt,siz[i]);ans[i]=x; 54 x+=cnt[x];cnt[x]++;x-=(cnt[x]-1); 55 update(rt,x,n,-siz[i]); 56 } for(int i=1;i<=n;i++) 57 printf("%d ",a[ans[i]]); 58 return 0; 59 }