bzoj 3295: [Cqoi2011]动态逆序对 cdq分治+树状数组

题目大意

给出1-n的一个排列和m个操作,每个操作有一个数字x表示在序列中把x这个数删掉,并且求在删掉x前该序列中有多少个逆序对。
n<=100000,m<=50000

分析

这题看完题后就想到了可以用树套树来搞,但是比较麻烦(其实也不是很麻烦)并且对于n<=100000,m<=50000的数据范围如果用nlog^2的复杂度去做的话会跑的很慢,于是就选择了新学的cdq分治。

先一开始往序列里面插入没被删除的数,然后从后往前的顺序处理操作。把每个操作拆分成两个操作,一个是插入,一个是询问,顺序不限。
那么问题就转换成了对于三元组(x,y,z)的处理,x是操作序号,y是下标,z是具体数值。对于这种问题一般的处理方式都是第一维直接排序,第二维cdq分治掉,第三维树状数组。
具体的就跟bzoj 3262一样啦。

注意要开long long

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#define N 100005
#define ll long long
using namespace std;

int c[N],a[N],pos[N],n,m,cnt,tot,vis[N],num[N],ans[N];
struct data{int x,pos,op,id;}q[N*10],t[N*10];

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 cdq(int l,int r)
{
    if (l>=r) return;
    int mid=(l+r)/2;
    cdq(l,mid);cdq(mid+1,r);
    for (int j=mid+1,i=l;j<=r;j++)
    {
        while (q[i].pos<q[j].pos&&i<=mid)
        {
            if (q[i].op==1) ins(q[i].x,1);
            i++;
        }
        if (q[j].op==2) ans[q[j].id]+=query(q[j].x-1);
    }
    for (int i=l,j=mid+1;j<=r;j++)
        while (q[i].pos<q[j].pos&&i<=mid)
        {
            if (q[i].op==1) ins(q[i].x,-1);
            i++;
        }
    for (int i=mid,j=r;j>mid;j--)
    {
        while (q[i].pos>q[j].pos&&i>=l)
        {
            if (q[i].op==1) ins(q[i].x,1);
            i--;
        }
        if (q[j].op==2) ans[q[j].id]+=query(n)-query(q[j].x);
    }
    for (int i=mid,j=r;j>mid;j--)
        while (q[i].pos>q[j].pos&&i>=l)
        {
            if (q[i].op==1) ins(q[i].x,-1);
            i--;
        }
    int i=l,j=mid+1,k=l;
    while (i<=mid||j<=r)
        if (q[i].pos<q[j].pos&&i<=mid||j>r) t[k++]=q[i++];
        else t[k++]=q[j++];
    for (int i=l;i<=r;i++)
        q[i]=t[i];
}

int main()
{
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        pos[a[i]]=i;
    }
    for (int i=1;i<=m;i++)
    {
        scanf("%d",&num[i]);
        vis[pos[num[i]]]=1;
    }
    ll sum=0;
    for (int i=1;i<=n;i++)
    {
        sum+=query(n)-query(a[i]);
        ins(a[i],1);
    }
    for (int i=1;i<=n;i++)
        ins(a[i],-1);
    for (int i=1;i<=n;i++)
        if (!vis[i])
        {
            q[++tot].op=1;
            q[tot].pos=i;
            q[tot].x=n-a[i]+1;
        }
    for (int i=m;i>=1;i--)
    {
        q[++tot].op=1;
        q[tot].pos=pos[num[i]];
        q[tot].x=n-num[i]+1;
        q[++tot].op=2;
        q[tot].pos=pos[num[i]];
        q[tot].x=n-num[i]+1;
        q[tot].id=++cnt;
    }
    cdq(1,tot);
    for (int i=cnt;i>=1;i--)
    {
        printf("%lld\n",sum);
        sum-=ans[i];
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值