【Luogu1393】动态逆序对(CDQ分治)

题面

题目描述

对于给定的一段正整数序列,我们定义它的逆序对的个数为序列中ai>aj且i < j的有序对(i,j)的个数。你需要计算出一个序列的逆序对组数及其删去其中的某个数的逆序对组数。

输入输出格式

输入格式:

第一行,两个数n,m,表示序列中有n个数,要删去m个数

第二行n个数,表示给定的序列。

第三行m个数,第i个数di表示要删去原序列中的第di个数。

输出格式:

一行m+1个数。第一个数表示给定序列的逆序对组数,第i+1个数表示删去第di个数后序列的逆序对组数(删去的数不再恢复)

输入输出样例

输入样例#1:

6 3
5 4 2 6 3 1
2 1 4

输出样例#1:

11 7 4 2

说明

对于20%的数据,n≤2500

对于另30%的数据,m=0

对于100%的数据,n≤40000,m≤n/2,且保证第二行n个数互不相同,第三行m个数互不相同

题解

之前不是说过要写一遍CDQ分治吗??
在这里说的
可是,当你把上面的代码兴高采烈的Copy到洛谷上之后
你就会直接WA了
因为,题目还是有点不同的(仔细读题)
区别一:这题不是排列,要离散化
区别二:这题删掉的不是数字,而是位置

好了回归正题,讲讲CDQ分治怎么写
首先,给所有删掉的数编个号,就按照删去的顺序来吧
没有删掉的数就编个INF吧

那么,删掉这个数之后,减少的逆序对对数是:
对于 j[1,j]
t[i]<t[j] ,其中t是删除的编号
并且
i<ja[i]>a[j]
或者
i>ja[i]<a[j]
所以,删除的编号直接sort搞完
剩下的两维CDQ分治

于是,发现这个玩意是一个三维偏序
所以之前写过的树状数组套平衡树当然也可以做啦
但是,CDQ分治还是要会嗷。。

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<set>
#include<map>
#include<vector>
#include<queue>
using namespace std;
#define MAX 50000
inline int read()
{
    int x=0,t=1;char ch=getchar();
    while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
    if(ch=='-')t=-1,ch=getchar();
    while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
    return x*t;
}
int n,m,S[MAX],a[MAX],b[MAX],c[MAX],d[MAX];
long long ans;
int lowbit(int x){return x&(-x);}
void Add(int x,int w){while(x<=n)c[x]+=w,x+=lowbit(x);}
int getsum(int x){int ret=0;while(x)ret+=c[x],x-=lowbit(x);return ret;}
struct Node
{
    int t,p,a;
    int s;
}t[MAX];
bool operator<(Node a,Node b){return a.t<b.t;}
bool cmp(Node a,Node b){return a.p<b.p;}
void CDQ(int l,int r)
{
    if(l==r)return;
    int mid=(l+r)>>1;
    CDQ(l,mid);CDQ(mid+1,r);
    sort(&t[l],&t[mid+1],cmp);
    sort(&t[mid+1],&t[r+1],cmp);
    int j=mid;
    for(int i=l;i<=mid;++i)
    {
        while(j<r&&t[j+1].p<t[i].p)++j,Add(t[j].a,1);
        t[i].s+=getsum(n)-getsum(t[i].a);
    }
    for(int i=mid+1;i<=j;++i)Add(t[i].a,-1);
    j=r+1;
    for(int i=mid;i>=l;--i)
    {
        while(j>mid+1&&t[j-1].p>t[i].p)--j,Add(t[j].a,1);
        t[i].s+=getsum(t[i].a-1);
    }
    for(int i=r;i>=j;--i)Add(t[i].a,-1);
}
int main()
{
    n=read();m=read();
    for(int i=1;i<=n;++i)S[i]=a[i]=read();
    sort(&S[1],&S[n+1]);
    for(int i=1;i<=n;++i)b[a[i]=lower_bound(&S[1],&S[n+1],a[i])-S]=i;
    for(int i=n;i;i--)ans+=getsum(a[i]),Add(a[i],1);
    for(int i=1;i<=n;++i)t[i].t=n+1,t[i].p=i,t[i].a=a[i];
    for(int i=1;i<=m;++i)
    {
        d[i]=read();
        t[d[i]].t=i;
    }
    sort(&t[1],&t[n+1]);
    memset(c,0,sizeof(c));
    CDQ(1,n);
    for(int i=1;i<=n;++i)c[t[i].p]=t[i].s;
    printf("%lld ",ans);
    for(int i=1;i<=m;++i)
        printf("%lld ",ans=ans-c[d[i]]);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值