洛谷P3157 cdq解法

该博客介绍了如何使用CDQ分治法处理动态逆序对问题,涉及数据结构如 Fenwick 树(二进制索引树)和节点的三维偏序关系。在一系列删除操作中,计算每次删除前序列的逆序对数量。通过维护元素的删除时间和权值,将二维偏序问题转化为三维偏序,并利用CDQ分治进行高效求解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题意:

给出一个nnn的排列,有mmm次删除操作,每次删除其中一个元素xxx,输出每次删除前序列的逆序对个数

cdq分治法:

与主席树相同的是,我们都是要删除一个位置时,这个位置所构成的逆序对个数,换句话说,就是求这个位置前面有多少大于xxx的,后面有多少小于xxx的。主席树能在线处理,动态修改权值线段树,使得某个区间的权值线段树任意查询,所以主席树可以在线处理这个问题。而换个角度思考,逆序对本身就是一种二维偏序关系,那在这里删除xxx,能产生影响的一定是符合逆序对偏序关系的,即位置在前的比自己大的,位置在后比自己小的。但是仅仅按照这样子计算出删除某个位置的影响不能双向修改,比如xxxyyy是一对逆序对,我们知道xxx的影响是1,删去xxxyyy的影响数也要减1。实际上删去xxx能少去多少逆序对仅跟满足偏序条件和当前仍然存在的元素有关系,所以给元素多加一个维度的属性,删去时间

那么要求的就是满足如下偏序关系的对数了:

t[i]t[i]t[i]iii元素删去的时间,val[i]val[i]val[i]iii元素的权值,pos[i]pos[i]pos[i]iii元素的位置,在t[i]<t[j]t[i]<t[j]t[i]<t[j]的条件下,符合

val[j]>val[i],pos[j]<pos[i] val[j]>val[i],pos[j]<pos[i] val[j]>val[i],pos[j]<pos[i]

val[j]<val[i],pos[j]>pos[i] val[j]<val[i],pos[j]>pos[i] val[j]<val[i],pos[j]>pos[i]

至此转换为一个三维偏序问题了,对两组偏序关系分别cdqcdqcdq分治即可

一些细节:

存在一些元素不被删去,那么一个办法是令不被删去的元素的删去时间是无穷大即可

需要求出一开始的逆序对数,一个方法是在输入的时候二维偏序求解逆序对,另一个方法是令不被删去的元素的删去时间分别是[∞,∞+1,∞+2,.....][\infty,\infty+1,\infty+2,.....][,+1,+2,.....],在设置为无穷大时把他们的顺序设为依次,所有位置的答案加起来就是逆序对个数

#include<bits/stdc++.h>
#define ll long long
using namespace std;

int n;

struct fenwick
{
    int tree1[200005],tree2[200005];
    inline int lowbit(int x){return -x&x;} 
    void add(int x,int v)
    {
        int tmp=x*v;
        while(x<=n)
        {
            tree1[x]+=v;
            tree2[x]+=tmp;
            x+=lowbit(x);
        }
    }
    void add(int l,int r,int k)
    {
        add(l,k);
        add(r+1,-k);
    }
    int getsum(int l,int r)
    {
        int ret=0,x=r;
        while(x)
        {
            ret+=(r+1)*tree1[x]-tree2[x];
            x-=lowbit(x);
        }
        x=l-1;
        while(x)
        {
            ret-=l*tree1[x]-tree2[x];
            x-=lowbit(x);
        }
        return ret;
    }
}tree;

struct node
{
    int val,t,pos;
}a[200005],tmp[200005];

int m,del[200005],pos[200005],ans[200005];

void solve1(int l,int r)
{
    if(l==r) return;
    int mid=l+r>>1;
    solve1(l,mid); solve1(mid+1,r);
    int j=l,i=mid+1,k=l;
    while(j<=mid&&i<=r)
    {
        if(a[j].pos<=a[i].pos)
        {
            tree.add(a[j].val,a[j].val,1);
            tmp[k++]=a[j++];
        }
        else
        {
            ans[a[i].pos]+=tree.getsum(a[i].val,n);
            tmp[k++]=a[i++];
        }
    }
    while(j<=mid)
    {
        tree.add(a[j].val,a[j].val,1);
        tmp[k++]=a[j++];
    }
    while(i<=r)
    {
        ans[a[i].pos]+=tree.getsum(a[i].val,n);
        tmp[k++]=a[i++];
    }
    for(int i=l;i<=mid;i++) tree.add(a[i].val,a[i].val,-1);
    for(int i=l;i<=r;i++) a[i]=tmp[i];
}

void solve2(int l,int r)
{
    if(l==r) return;
    int mid=l+r>>1;
    solve2(l,mid); solve2(mid+1,r);
    int j=l,i=mid+1,k=l;
    while(j<=mid&&i<=r)
    {
        if(a[j].pos>=a[i].pos)
        {
            tree.add(a[j].val,a[j].val,1);
            tmp[k++]=a[j++];
        }
        else
        {
            ans[a[i].pos]+=tree.getsum(1,a[i].val);
            tmp[k++]=a[i++];
        }
    }
    while(j<=mid)
    {
        tree.add(a[j].val,a[j].val,1);
        tmp[k++]=a[j++];
    }
    while(i<=r)
    {
        ans[a[i].pos]+=tree.getsum(1,a[i].val);
        tmp[k++]=a[i++];
    }
    for(int i=l;i<=mid;i++) tree.add(a[i].val,a[i].val,-1);
    for(int i=l;i<=r;i++) a[i]=tmp[i];
}

ll tot;
int q[200005];

int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i].val);
        a[i].pos=i;
        pos[a[i].val]=i;
    }
    for(int i=1;i<=m;i++)
    {
        scanf("%d",&q[i]);
        a[pos[q[i]]].t=i;
    };
    int pp=0x3fffffff;
    for(int i=1;i<=n;i++)
        if(!a[i].t) a[i].t=++pp;
    sort(a+1,a+1+n,[&](const node& x,const node& y){
        if(x.t!=y.t) return x.t>y.t;
        if(x.pos!=y.pos) return x.pos<y.pos;
        return x.val>y.val;
    }); 
    solve1(1,n);
    sort(a+1,a+1+n,[&](const node& x,const node& y){
        if(x.t!=y.t) return x.t>y.t;
        if(x.pos!=y.pos) return x.pos>y.pos;
        return x.val<y.val;
    }); 
    solve2(1,n);
    for(int i=1;i<=n;i++) tot+=ans[i];
    for(int i=1;i<=m;i++)
    {
        printf("%lld\n",tot);
        tot-=ans[pos[q[i]]];
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值