【cdq分治】[HYSBZ/BZOJ3295]动态逆序对

题目

看看这篇博客写的时间,BZOJ已经挂了,我就不粘BZOJ链接了。

Description

对于序列A,它的逆序对数定义为满足i < j,且Ai > Aj的数对(i,j)的个数。给1到n的一个排列,按照某种顺序依次删除m个元素,你的任务是在每次删除一个元素之前统计整个序列的逆序对数。

Input

输入第一行包含两个整数n和m,即初始元素的个数和删除的元素个数。以下n行每行包含一个1到n之间的正整数,即初始排列。以下m行每行一个正整数,依次为每次删除的元素。

Output

输出包含m行,依次为删除每个元素之前,逆序对的个数。

Sample Input

5 4
1
5
3
4
2
5
1
4
2
Sample Output

5
2
2
1
Hint

样例解释
(1,5,3,4,2)>>(1,3,4,2)>>(3,4,2)>>(3,2)>>(3)。

数据范围

编号 1-2 3-4 5-6 7-8 9-10

n <=1000 <=30000 <=50000 <=60000 <=100000

m <=100 <=10000 <=20000 <=40000 <=50000

分析

这道题

我们令y表示当前数字,t表示y被加入(先被删除的后加入)的时间,x表现y在原串中的位置,这样一个数字就变成了一个三元组(t,x,y)。
不难发现,当一个数字(t0,x0,y0)对最终的逆序对数作出的贡献为存在的三元组(t,x,y)使得 (t<t0,(x<x0)xor(y<y0)==1) 的数量。
这样,我们就可以用cdq分治做这道题。
如果你已经会cdq请直接看代码。

如何用cdq分治做这道题

This part was powered by azui
t<t0,x<x0,y>y0 或者 t<t0,x>x0,y<y0
我们先考虑满足条件一: t<t0,x<x0,y>y0 的点。
在外面按x排序后,还剩下t,y两个参数。我们可以对t进行划分排序,使得t,x满足这里写图片描述
然后就可以求出对于每一个右边的三元组(t0,x0,y0),有多少个点满足 t<t0,x<x0 (类似于归并排序),然后用树状数组维护这些三元组中有哪些 y>y0
条件二类似。
然后递归处理左右两边。

感谢azui大神,你们也可以去看他自己的博客

代码

#include<cstdio>
#include<algorithm>
#define MAXN 100000
using namespace std;
struct node{
    int t,x,y;
    node(){
    }
    node(int tt,int xx,int yy){
        t=tt,x=xx,y=yy;
    }
}p[MAXN+10],tmp[MAXN+10];
int n,m,a[MAXN+10],t[MAXN+10],c[MAXN+10];
long long ans[MAXN+10];
void Read(int &x){
    char c;
    while(c=getchar(),c!=EOF)
        if(c>='0'&&c<='9'){
            x=c-'0';
            while(c=getchar(),c>='0'&&c<='9')
                x=x*10+c-'0';
            ungetc(c,stdin);
            return;
        }
}
inline int lowbit(int x){
    return x&-x;
}
void update(int x,int d){
    while(x<=n){
        c[x]+=d;
        x+=lowbit(x);
    }
}
int get_sum(int x){
    int ret=0;
    while(x){
        ret+=c[x];
        x-=lowbit(x);
    }
    return ret;
}
void read(){
    Read(n),Read(m);
    int i,b,j=0;
    for(i=1;i<=n;i++)
        Read(a[i]);
    for(i=1;i<=m;i++){
        Read(b);
        t[b]=i;
    }
    for(i=1;i<=n;i++)
        if(t[a[i]])
            p[i]=node(n-t[a[i]]+1,i,a[i]);
        else
            p[i]=node(++j,i,a[i]);
}
void cdq(int l,int r){
    if(l==r)
        return;
    int mid=(l+r)>>1,i,j,k;
    k=mid+1,j=l;
    for(i=l;i<=r;i++)
        if(p[i].t<=mid)
            tmp[j++]=p[i];
        else
            tmp[k++]=p[i];
    for(i=l;i<=r;i++)
        p[i]=tmp[i];
    i=l;
    for(j=mid+1;j<=r;j++){
        for(;i<=mid&&p[i].x<p[j].x;i++)
            update(p[i].y,1);
        ans[p[j].t]+=(i-l)-get_sum(p[j].y);
    }
    for(i--;i>=l;i--)
        update(p[i].y,-1);
    i=mid;
    for(j=r;j>mid;j--){
        for(;i>=l&&p[i].x>p[j].x;i--)
            update(p[i].y,1);
        ans[p[j].t]+=get_sum(p[j].y);
    }
    for(i++;i<=mid;i++)
        update(p[i].y,-1);
    cdq(l,mid);
    cdq(mid+1,r);
}
void print(){
    int i;
    for(i=2;i<=n;i++)
        ans[i]+=ans[i-1];
    for(i=n;i>n-m;i--)
        printf("%I64d\n",ans[i]);
}
int main()
{
    read();
    cdq(1,n);
    print();
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值