[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;
}
posted @ 2020-01-15 13:56  花淇淋  阅读(77)  评论(0编辑  收藏  举报