[贪心 线段树] LOJ#2472. 「九省联考 2018」IIIDX

从1到n枚举,逐位确定。

首先可以把关系树建出来,一个点的权值要大于等于父节点的权值。

如果没有相同数字的,第 i i 以及它子树种的点会选择 [nsizei+1,n] 这个区间里的数,选完后把这个区间删去,继续考虑 i+1 i + 1

如果有重复的数字,那么第 i i 个点会选择第 nsizei+1 大的数字,但是它的子树中的点选择的区间就不能确定。

ai a i 表示大于等于 i i 的可以选择的数的个数,bi 表示已经选了多少个大于等于 i i 的数

那么如果当前考虑 i 点, i i 点选择的数为 p p p 肯定会尽可能的大),那么我们就会选择 sizei 个大于等于 p p 的数。

可以发现,如果 i 能选择 p p ,那么对于任何一个小于等于 p j j 都满足 bj+sizeiaj

也就是 sizeiajbj s i z e i ≤ a j − b j ,用线段树记录 ajbj a j − b j ,然后在线段树上二分一下 p p <script type="math/tex" id="MathJax-Element-92">p</script> 就可以了

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstdlib>
#include <vector>
#define ALL(x) (x).begin(),(x).end()

using namespace std;

typedef pair<int,int> pii;

const int N=500010;

int n,cnt,b[N],a[N],G[N],size[N],ans[N],son[N],dpt[N],fa[N];
double k;
struct edge{
    int t,nx;
}E[N];

vector<int> sb;

inline void addedge(int x,int y){
    E[++cnt].t=y; E[cnt].nx=G[x]; G[x]=cnt;
}

void dfs(int x){
    size[x]=1;
    for(int i=G[x];i;i=E[i].nx){
        dpt[E[i].t]=dpt[x]+1;
        dfs(E[i].t);
        size[x]+=size[E[i].t];
        fa[E[i].t]=x;
    }
}

int mn[N<<2],tag[N<<2];

inline void Up(int g){
    mn[g]=min(mn[g<<1]+tag[g<<1],mn[g<<1|1]+tag[g<<1|1]);
}

void build(int g,int l,int r){
    if(l==r) return mn[g]=b[l],void();
    int mid=l+r>>1;
    build(g<<1,l,mid); build(g<<1|1,mid+1,r);
    Up(g);
}

vector<int> Q[N];

void Modify(int g,int l,int r,int L,int R,int x){
    if(l==L && r==R) return tag[g]+=x,void();
    int mid=L+R>>1;
    if(r<=mid) Modify(g<<1,l,r,L,mid,x);
    else if(l>mid) Modify(g<<1|1,l,r,mid+1,R,x);
    else Modify(g<<1,l,mid,L,mid,x),Modify(g<<1|1,mid+1,r,mid+1,R,x);
    Up(g);
}

int Query(int s){
    int g=1,l=1,r=n,ret;
    while(l<r){
        int mid=l+r>>1; s-=tag[g];
        if(mn[g<<1]+tag[g<<1]<s) r=mid,g=g<<1;
        else l=mid+1,g=g<<1|1,ret=mid;
    }
    if(mn[g]+tag[g]>=s) return l;
    return ret;
}

int main(){
    scanf("%d%lf",&n,&k);
    for(int i=1;i<=n;i++)   
        scanf("%d",&a[i]),sb.push_back(a[i]);
    sb.push_back(0); sort(ALL(sb)); sb.erase(unique(ALL(sb)),sb.end());
    for(int i=1;i<=n;i++) a[i]=lower_bound(ALL(sb),a[i])-sb.begin();
    for(int i=1;i<=n;i++) addedge(i/k,i);
    dfs(0);
    for(int i=1;i<=n;i++) b[a[i]]++;
    for(int i=n;i;i--) b[i]+=b[i+1];
    build(1,1,n);
    for(int i=1;i<=n;i++) Q[dpt[i]].push_back(i);
    for(int d=1;d<=dpt[n];d++){
        for(int i=0;i<Q[d].size();i++){
            if(d>1 && (i==0 || fa[Q[d][i]]!=fa[Q[d][i-1]]))
                Modify(1,1,ans[fa[Q[d][i]]],1,n,size[fa[Q[d][i]]]-1);
            int u=Q[d][i],p=Query(size[u]);
            ans[u]=p; Modify(1,1,p,1,n,-size[u]);
        }
    }
    for(int i=1;i<=n;i++) printf("%d ",sb[ans[i]]);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值