【codeforces 1208D】 Restore Permutation(线段树)

题面

题意:

一个长度为 n n n的排列 a a a,现在定义 p i p_i pi为数组 a a a中下标小于等于 i i i并且小于 a i a_i ai的数字的和。现在给定 p p p,求 a a a

思路:

首先可以肯定的是, p p p中最后一个 0 0 0出现的位置 p o s pos pos a a a中一定是 1 1 1。我们可以反证:
假设 a p o s a_{pos} apos不为 1 1 1,假设 1 1 1 p o s pos pos之前,那么 p p o s ≥ 1 p_{pos} \ge 1 ppos1;假设 1 1 1 p o s pos pos之后,那么 p o s pos pos一定不是最后一个 0 0 0出现的位置,因为当 a p o s = 1 a_{pos}=1 apos=1,那么 p p o s p_{pos} ppos一定等于 0 0 0,因此最后一个 0 0 0出现的位置一定是 1 1 1,那么我们把 1 1 1放到 p o s pos pos处。并且把 i i i之后的 p j p_{j} pj都减去 1 1 1.

其实对于从 2 2 2 n n n的处理方法都一样,对于第 i i i次操作,找到 p p p中最后一个为 0 0 0的位置 p o s pos pos,令 a p o s a_{pos} apos i i i,然后对于所有的 i ≥ p o s i \ge pos ipos,让 p i − i p_{i}-i pii。最终即可得到 a a a数组。对于上面的操作,可以用线段树。

线段树寻找最后一个 0 0 0的位置:从根节点出发,先看右子树的最小值是否为 0 0 0,如果是,去查右子树,否则查左子树。
最终复杂度 O ( n ⋅ ( l o g ( n ) ) 2 ) O(n \cdot (log(n))^2) O(n(log(n))2)

代码:

#include<bits/stdc++.h>
using namespace std;
const int N = 1e6+100;
const int mod = 1e9+7;
typedef long long ll;
const int INF = 0x3f3f3f3f;
const ll llINF = 0x3f3f3f3f3f3f3f3f;
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define fep(i,a,b) for(int i=(a);i>=(b);i--)
inline bool read(ll &num) {
    char in;bool IsN=false;
    in=getchar();
    if(in==EOF) return false;
    while(in!='-'&&(in<'0'||in>'9')) in=getchar();
    if(in=='-'){ IsN=true;num=0;}
    else num=in-'0';
    while(in=getchar(),in>='0'&&in<='9'){
            num*=10,num+=in-'0';
    } 
    if(IsN) num=-num;
    return true;
}
ll minn[N],sum[N],tag[N],p[N],a[N],n;
void pushup(ll rt){
    minn[rt]=min(minn[rt<<1|1],minn[rt<<1]);
    return ;
}
void pushdown(ll rt,ll l,ll r){
    ll mid=(l+r)>>1;
    tag[rt<<1]+=tag[rt];
    tag[rt<<1|1]+=tag[rt];
    minn[rt<<1]+=tag[rt];
    minn[rt<<1|1]+=tag[rt];
    tag[rt]=0;
}
void buildtree(ll rt,ll l,ll r){
    tag[rt]=0;
    if(l==r){
        minn[rt]=p[l];
        return ;
    }
    int mid=(l+r)>>1;
    buildtree(rt<<1,l,mid);
    buildtree(rt<<1|1,mid+1,r);
    pushup(rt);
}
void modify(ll nl,ll nr,ll l,ll r,ll rt,ll k){//区间加减
    if(nl<=l&&r<=nr){
        tag[rt]+=k;
        minn[rt]+=k;
        return ;
    }
    ll mid=(l+r)>>1;
    if(nl<=mid) modify(nl,nr,l,mid,rt<<1,k);
    if(nr>mid) modify(nl,nr,mid+1,r,rt<<1|1,k);
    pushup(rt);
}
ll ask(ll rt,ll l,ll r,ll nl,ll nr){//查询区间最小值
    if(nl<=l&&r<=nr){
        return minn[rt];
    }
    pushdown(rt,l,r);
    ll mid=(l+r)>>1;
    ll ans=9999999999;
    if(nl<=mid) ans=min(ans,ask(rt<<1,l,mid,nl,nr));
    if(nr>mid) ans=min(ans,ask(rt<<1|1,mid+1,r,nl,nr));
    return ans;
}
ll query(ll rt,ll l,ll r){//查找最后一个0的位置
    if(l==r){
        return l;
    }
    ll mid=(l+r)>>1;
    if(ask(1,1,n,mid+1,r)==0) return query(rt<<1|1,mid+1,r);
    else return query(rt<<1,l,mid);
    pushup(rt);
}
int main(){
    read(n);
    rep(i,1,n) read(p[i]);
    buildtree(1,1,n);
    rep(i,1,n){
        int id=query(1,1,n);
        a[id]=i;
        modify(id+1,n,1,n,1,-i);
        modify(id,id,1,n,1,100000000000+i);//找到位置后把这个变成大数
    }
    rep(i,1,n) cout<<a[i]<<' ';
    cout<<endl;
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值