bzoj 3333: 排队计划 树状数组+线段树

来源:互联网 发布:微课录屏软件有哪些 编辑:程序博客网 时间:2024/06/05 03:12

题意

给出一个长度为n的序列A,有m次操作,每次操作会选择一个位置x,然后把该位置后面(包括该位置)所有不大于A[x]的数拿出来重新排列后再放回去。要求在每次操作后输出逆序对数。
n,m<=500000

分析

我们定义一个位置x的贡献为x后面有多少个比A[x]小的数。那么逆序对数就是每个位置贡献的和。
注意到每次操作会吧位置x后面所有不大于x的位置的贡献变为0,那么只要用一棵线段树来维护即可。

代码

#include<iostream>#include<cstdio>#include<cstdlib>#include<cstring>#include<algorithm>using namespace std;typedef long long LL;const int N=500005;const int inf=0x3f3f3f3f;int n,m,f[N],a[N],w[N],c[N];struct tree{int mn;}t[N*5];LL ans;int read(){    int x=0,f=1;char ch=getchar();    while (ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}    while (ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}    return x*f;}void ins(int x,int y){    while (x<=n) c[x]+=y,x+=x&(-x);}int query(int x){    int ans=0;    while (x) ans+=c[x],x-=x&(-x);    return ans;}void updata(int d){    t[d].mn=min(t[d*2].mn,t[d*2+1].mn);}void build(int d,int l,int r){    if (l==r)    {        if (f[l]) t[d].mn=a[l];        else t[d].mn=inf;        return;    }    int mid=(l+r)/2;    build(d*2,l,mid);build(d*2+1,mid+1,r);    updata(d);}void solve(int d,int l,int r,int x,int y,int z){    if (t[d].mn>z||x>y) return;    if (l==r)    {        ans-=f[l];f[l]=0;t[d].mn=inf;        return;    }    int mid=(l+r)/2;    solve(d*2,l,mid,x,min(y,mid),z);    solve(d*2+1,mid+1,r,max(x,mid+1),y,z);    updata(d);}int main(){    n=read();m=read();    for (int i=1;i<=n;i++) a[i]=read(),w[i]=a[i];    sort(w+1,w+n+1);    int w1=unique(w+1,w+n+1)-w-1;    for (int i=1;i<=n;i++) a[i]=lower_bound(w+1,w+w1+1,a[i])-w;    for (int i=n;i>=1;i--)    {        f[i]=query(a[i]-1);        ins(a[i],1);        ans+=f[i];    }    build(1,1,n);    printf("%lld\n",ans);    while (m--)    {        int x=read();        solve(1,1,n,x,n,a[x]);        printf("%lld\n",ans);    }    return 0;}
原创粉丝点击