[BZOJ3333]排队计划(离散化+树状数组+线段树)

70 篇文章 0 订阅
33 篇文章 0 订阅

题目描述

传送门

题解

离散化。记高度为h。
首先用树状树组求出逆序对即为第一问的答案。
求出每个点后面有几个比它小记为rev[i],即为它对总逆序对数的贡献。建立线段树维护区间h的最小值。
对于每次询问的点P,不停地找区间[P,n]的最小值i,然后将h[i]赋为正无穷,直到点P的h为最小值停止。每次找到最小值的点都将ans减去那个点的rev。
这样做的基础是基于一个非常有用的性质:逆序对数只会不断减少,因为对于每一次操作,对于P前面的点没有影响,对于它后面的比P大的点也没有影响,而对于后面比它小的点,只有他们互相之间是有影响的,这个影响就是rev都变为0,即不会再产生后面的比它小的数了。
由于每个点只会被修改一次,那么均摊时间复杂度为O(nlogn)。

代码

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
#define LL long long
const int max_n=5e5+5;
const int max_tree=max_n*4;
const int INF=2e9;

int n,m,x,y,cnt,a[max_n],p[max_n],h[max_n];
LL rev[max_n],ans,C[max_n];
int tree[max_tree];

inline int cmp(int x,int y){return a[x]<a[y];}
inline void Bit_add(int loc,int val){
    for (int i=loc;i<=cnt;i+=i&(-i)) C[i]+=val;
}
inline LL Bit_query(int loc){
    LL ans=0;
    for (int i=loc;i>=1;i-=i&(-i)) ans+=(LL)C[i];
    return ans;
}
inline void Segtree_update(int now){
    if (h[tree[now<<1]]<h[tree[now<<1|1]]) tree[now]=tree[now<<1];
    else tree[now]=tree[now<<1|1];
}
inline void Segtree_build(int now,int l,int r){
    int mid=(l+r)>>1;
    if (l==r){
        tree[now]=l;
        return;
    }
    Segtree_build(now<<1,l,mid);
    Segtree_build(now<<1|1,mid+1,r);
    Segtree_update(now);
}
inline void Segtree_change(int now,int l,int r,int x){
    int mid=(l+r)>>1;
    if (l==r){
        tree[now]=l;
        return;
    }
    if (x<=mid) Segtree_change(now<<1,l,mid,x);
    else Segtree_change(now<<1|1,mid+1,r,x);
    Segtree_update(now);
}
inline int Segtree_query(int now,int l,int r,int lrange,int rrange){
    int mid=(l+r)>>1,ans=INF,anst;
    if (lrange<=l&&r<=rrange) return tree[now];
    if (lrange<=mid){
        int t=Segtree_query(now<<1,l,mid,lrange,rrange);
        if (ans>=h[t]) ans=h[t],anst=t;
    }
    if (mid+1<=rrange){
        int t=Segtree_query(now<<1|1,mid+1,r,lrange,rrange);
        if (ans>=h[t]) ans=h[t],anst=t;
    }
    return anst;
}
int main(){
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;++i) scanf("%d",&a[i]),p[i]=i;
    sort(p+1,p+n+1,cmp);
    for (int i=1;i<=n;++i)
      if (a[p[i]]!=a[p[i-1]]) h[p[i]]=++cnt; else h[p[i]]=cnt;
    for (int i=n;i>=1;--i)
      rev[i]=Bit_query(h[i]-1),ans+=rev[i],Bit_add(h[i],1);
    printf("%lld\n",ans);
    Segtree_build(1,1,n);
    for (int i=1;i<=m;++i){
        scanf("%d",&x);
        while (h[x]!=INF){
            y=Segtree_query(1,1,n,x,n);
            h[y]=INF;
            ans-=rev[y];
            Segtree_change(1,1,n,y);
        }
        printf("%lld\n",ans);
    }
}

总结

刚开始的思路好像有点对,但是想了一个树套树233

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值