BZOJ5249 [2018多省省隊聯測]IIIDX(神奇的貪心)

題目鏈接:BZOJ 5249

題目大意:n(n<=500000)首曲子,n個難度值d[i] (d[i]<=1000000000)。給定一個實數k (k<=1000000000),完成第ik 首曲子後才能解鎖第i首曲子,若ik=0 ,說明第i首曲子無需解鎖。確定一種字典序最大的難度順序,保證每次解鎖的曲子的難度不低於作爲條件需要玩家通關的曲子的難度。

題解:
可以把 ik 和 i 分別看成父節點和子節點,就可以將題意轉化爲:n個節點的一片森林,n個權值,要給每個節點分配一個權值,保證子節點的權值不小於父節點的權值,並且1~n的權值的字典序最大。
考慮貪心。按1~n的順序分配難度,每次確定一個節點的難度時,在能夠滿足子樹內所有節點的難度不小於當前點難度的情況下,最大化當前難度,這樣貪心出來的就會是字典序最大的方案。
實現的時候,先把難度值從大到小排序,對每個位置i,維護res[i]表示它及它的左側尚未被分配的難度值的個數。每次確定一個節點u的難度時,需要找到一個難度值最大且儘可能靠右的位置 pos,滿足res[i]>=siz[u] (pos<=i<=n)(siz[u]是以u爲根的子樹的大小),d[pos]就是u的難度值了。但確定了u的難度後,還無法確定其子樹各點具體的難度值,所以需要先爲其子樹預留一部分難度值,即res[i]-=siz[u] (pos<=i<=n)。這就對應着線段樹上二分和區間加減。注意之後再處理u的子樹時要把預留的權值加回去。

具體實現起來還是很巧妙的 (✪ω✪) ~

code(有參考大神的BLOG)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define N 500005
#define inf 100000000
using namespace std;
inline int read()
{
    char c=getchar(); int num=0,f=1;
    while (c<'0'||c>'9') { if (c=='-') f=-1; c=getchar(); }
    while (c<='9'&&c>='0') { num=num*10+c-'0'; c=getchar(); }
    return num*f;
}
int n,res[N<<2],tag[N<<2],a[N],b[N],ans[N],siz[N],fa[N],cnt[N];
double k;
bool cmp(int a,int b) { return a>b; }
void pushup(int now)
{
    res[now]=min(res[now<<1],res[now<<1|1]);
}
void pushdown(int now)
{
    tag[now<<1]+=tag[now]; res[now<<1]+=tag[now];
    tag[now<<1|1]+=tag[now]; res[now<<1|1]+=tag[now];
    tag[now]=0;
}
void build(int now,int l,int r)
{
    if (l==r) { res[now]=l; return; }
    int mid=l+r>>1;
    build(now<<1,l,mid);
    build(now<<1|1,mid+1,r);
    pushup(now);
}
int query(int now,int l,int r,int k)
{
    if (l==r) return (res[now]>=k)?l:l+1;
    int mid=l+r>>1; pushdown(now);
    if (k<=res[now<<1|1]) return query(now<<1,l,mid,k);
    return query(now<<1|1,mid+1,r,k);
}
void update(int now,int l,int r,int begin,int end,int val)
{
    if (begin<=l&&r<=end)
    {
        res[now]+=val; tag[now]+=val; return;
    }
    int mid=l+r>>1; pushdown(now);
    if (begin<=mid) update(now<<1,l,mid,begin,end,val);
    if (end>mid) update(now<<1|1,mid+1,r,begin,end,val);
    pushup(now);
}
int main()
{
    n=read(); scanf("%lf",&k);
    for (int i=1;i<=n;i++) a[i]=read();
    sort(a+1,a+n+1,cmp);
    for (int i=n-1;i>=1;i--)
     if (a[i]==a[i+1]) cnt[i]=cnt[i+1]+1; else cnt[i]=0;
    for (int i=1;i<=n;i++) fa[i]=(int)floor(i/k),siz[i]=1;
    for (int i=n;i>=1;i--) siz[fa[i]]+=siz[i];
    build(1,1,n);
    for (int i=1;i<=n;i++)
    {
        if (fa[i]&&fa[i]!=fa[i-1])
         update(1,1,n,ans[fa[i]],n,siz[fa[i]]-1); //加回去
        int x=query(1,1,n,siz[i]); //在線段樹上二分
        x+=cnt[x];  cnt[x]++;  x-=(cnt[x]-1); //使pos儘可能靠右
        ans[i]=x;
        update(1,1,n,x,n,-siz[i]); //預留權值
    }
    for (int i=1;i<=n;i++) printf("%d ",a[ans[i]]);
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章