• [BZOJ3295][CQOI2011]动态逆序对(cdq分治+树状数组)


    Solution

    此题可以十分简单粗暴地套用树状数组套主席树的模板。
    或者:cdq分治。

    此题中,原先给出一个数列,之后会删除一些数。但是,呃,删除操作好像有点儿麻烦。反正允许离线,那就当作是初始给出一些数,先把这些数加入序列,之后会再添加一些数。也就是全部反过来算。

    可以把每次的答案分成两个部分:原先存在的逆序对+加入这个数新产生的逆序对,那么每次只要算出当前新产生的逆序对,最后算一遍前缀和即可。

    加入这个数新产生的逆序对也可以分成两个部分:位置靠前且值比它大的,位置靠后且比它小的。那么总共有三种操作,(1)往序列的某个位置加入一个数,(2)查询比某个数位置靠前且值比它大的数量,(3)查询比某个数位置靠后且值比它小的数量。显然,根据给出的删除顺序,可以得出每个操作的先后顺序,即每个操作的时间。要在cdq分治之前,要先按操作的时间从小到大排序。

    在cdq分治中,每次会合并左右两个序列。在合并的时候,如果要查询位置靠前且比它大的,就要先执行位置靠前的操作,位置相同的操作,先执行加入操作,树状数组维护的是值,也就是说cdq分治结束以后,所有操作将按位置从小到大顺序排好。这样不方便查询位置靠后且比它小的。

    那么,考虑把所有的操作分成互不影响的两组操作,第一组:所有的操作(1)和操作(2)。第二组:所有的操作(1)和操作(3)。两组操作分别先按时间从小到大排序,第二组操作在cdq分治的时候,先执行值小的操作,树状数组维护的是位置。

    最后把两组操作所得结果累计,就得到了每次答案的加入这个数新产生的逆序对这一部分。

    代码建议自己完成,实在不行的话,下面那个代码凑合着看吧QAQ

    Code

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    #define lowbit(x) (x&(-x))
    inline int getint()//读入优化 
    {
        char ch;
        int res=0;
        while(ch=getchar(),ch<'0'||ch>'9');
        res=ch-48;
        while(ch=getchar(),ch>='0'&&ch<='9')
        res=res*10+ch-48;
        return res;
    }
    const int e=3e5+5;
    struct point
    {
        int id,v,t,opt,k;
    }a[e],t[e],b[e];//id是位置,v是数值。k其实没什么用,然而不知道为什么写进去了。a,b为两组操作
    int n,m,real[e],c[e],cnt,num;
    bool vis[e];
    long long ans[e];//逆序对总数可能超过int范围
    inline bool cmp(const point &a,const point &b)//排序 
    {
        return a.t<b.t||(a.t==b.t&&a.opt<b.opt);//t是时间,opt=1为加入操作,opt=2为询问操作
    }
    inline void init(int x)//树状数组清零 
    {
        while(x<=n)
        {
            c[x]=0;
            x+=lowbit(x);
        }
    }
    inline void add(int x,int d)//树状数组单点修改
    {
        while(x<=n)
        {
            c[x]+=d;
            x+=lowbit(x);
        }
    }
    inline int query(int x)//树状数组前缀和查询
    {
        int res=0;
        while(x)
        {
            res+=c[x];
            x-=lowbit(x);
        }
        return res;
    }
    inline void solve(int l,int r)//第一组分治 
    {
        if(l==r)return;
        int mid=(l+r)/2,id1=l,id2,i;
        id2=mid+1;
        solve(l,mid);
        solve(mid+1,r);
        for(i=l;i<=r;i++)
        if(id2>r||id1<=mid&&a[id1].id<=a[id2].id)//先执行位置靠前的
        {
            t[i]=a[id1++];
            if(t[i].opt==1)
            add(t[i].v,1);
        }
        else
        {
            t[i]=a[id2++];
            if(t[i].opt==2)
            ans[t[i].k]+=(long long)query(n)-(long long)query(t[i].v);
            //查询值比它大的数量
        }
        for(i=l;i<=r;i++)
        {
            a[i]=t[i];
            init(a[i].v);
        }
    }
    inline void solve2(int l,int r)//第二组分治 
    {
        if(l==r)return;
        int mid=(l+r)/2,id1=l,id2,i;
        id2=mid+1;
        solve2(l,mid);
        solve2(mid+1,r);
        for(i=l;i<=r;i++)
        if(id2>r||id1<=mid&&b[id1].v<=b[id2].v)//先执行值较小的
        {
            t[i]=b[id1++];
            if(t[i].opt==1)
            add(t[i].id,1);
        }
        else
        {
            t[i]=b[id2++];
            if(t[i].opt==2)
            ans[t[i].k]+=(long long)query(n)-(long long)query(t[i].id);
            //查询位置比它靠后的数量
        }
        for(i=l;i<=r;i++)
        {
            b[i]=t[i];
            init(b[i].id);
        }
    }
    int main()
    {
        int i,x,y;
        n=getint();
        m=getint();
        for(i=1;i<=n;i++)
        {
            x=getint();
            real[x]=i;//x的位置为i
        }
        for(i=1;i<=m;i++)//创建操作 
        {
            x=getint();
            y=real[x];//y为x的位置
            vis[x]=true; 
            a[++cnt]=(point){y,x,n-i+1,1,0};
            a[++cnt]=(point){y,x,n-i+1,2,n-i+1};
            b[++num]=(point){y,x,n-i+1,1,0};
            b[++num]=(point){y,x,n-i+1,2,n-i+1};
        }
        int ti=0;
        for(i=1;i<=n;i++)//创建操作 
        if(!vis[i])
        {
            a[++cnt]=(point){real[i],i,++ti,1,0};
            a[++cnt]=(point){real[i],i,ti,2,ti};
            b[++num]=(point){real[i],i,ti,1,0};
            b[++num]=(point){real[i],i,ti,2,ti};
        }
        sort(a+1,a+cnt+1,cmp);//排序 
        sort(b+1,b+num+1,cmp);
        solve(1,cnt);
        solve2(1,num);
        for(i=2;i<=n;i++)//做一遍前缀和 
        ans[i]+=ans[i-1];
        for(i=n;i>=n-m+1;i--)
        printf("%lld\n",ans[i]);
        return 0;
    }
    
  • 相关阅读:
    Guns项目整体结构
    基于事件的NIO多线程服务器
    Reactor模式和NIO
    ConcurrentHashMap之实现细节
    C 语言的前世今生
    Netty系列之Netty高性能之道
    java synchronized详解
    生产者/消费者模式
    当spring 容器初始化完成后执行某个方法
    Linux系统管理员需要知道的16个服务器监控命令
  • 原文地址:https://www.cnblogs.com/cyf32768/p/12196305.html
Copyright © 2020-2023  润新知