[九省聯考2018] IIIDX 線段樹+貪心

題目:

  給出 k 和 n 個數,構造一個序列使得 d[i]>=d[i/k] ,而且字典序最大。c++

 

分析:

  據說,當年省選的時候,這道題擋住了大批的高手,看上去十分簡單,實際上那道彎段時間內是轉不過來的。ide

  首先,一個套路是,將這個序列的關係抽象成一棵樹,i的父親是floor(i/k),咱們要要求子樹內部的點的權值都比父親大。ui

  咱們觀察子任務的特殊限制,di不同?spa

  咱們想,把原序列從大到小排序,在樹上dfs給點賦值,在給一個點賦值時,要在序列上預留出siz[這棵子樹]的位置,用來給子樹內部的點賦值(排序就是爲了方便在序列上預留位置)。code

  可是,若是有重複的元素就辦不了了,好比n=4, k=2 序列是1,1,1,2.
blog

  排好應該是1 1 2 1,可是咱們上面的方法會排成1 1 1 2
排序

  爲何呢?遞歸

  咱們從大到小排序,應該獲得2 1 1 1.咱們遞歸,首先是樹根節點,須要預留4個位置,因此確定要選最後一個(1),而後遞歸到第一個子樹(序號爲2),size爲2,須要在這個子樹留兩個位置,因此咱們把第二個1給了序號2這個位置,而後那個2就給了它的兒子(序號4)可是實際上,咱們能夠給它留一個1,把這個4給位置3.
it

  我這麼分析會很亂,可是咱們於情於理考慮一下,咱們解決完了位置2,若想字典序最大,咱們應該先最大化3這個位置。event

  因此咱們應該在給序號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 }
貪心+線段樹
相關文章
相關標籤/搜索