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;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值