【BZOJ-3295】动态逆序对

题目链接

题目描述

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

题解

CDQ分治
涉及到删除一个数,怎么搞,想了也没有好的解决方法。
我们只喜欢统计某元素贡献而不会删除元素贡献。
那就把删除改为插入就行了。越早删掉的可以视为越晚插入,每有删的则视为依次从最开始插入即可。
再来考虑怎么做。
假设在某个时间点 t0 t 0 要求 p0 p 0 位置上的数 x0 x 0 的对此时答案的贡献,且保证不重复。
那么对于 p<p0 p < p 0 的数,当 t<t0,x>x0 t < t 0 , 且 x > x 0 时该数有贡献。
仅仅这样够吗?
因为当前面有满足 p<p0,t>t0,x>x0 p < p 0 , t > t 0 , x > x 0 的数x存在时, x0 x 0 x x 有贡献,但这时统计不到。所以还要看某个数后面的数,把t<t0,x<x0的数也算一遍贡献就行了。
并且这样不会算重,因为每个数只算了比它先出现的数,不会算重。
统计答案时,一个数的贡献会一直持续,相当于是对答案进行一个后缀加,随便差分一下就可以了。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<queue>
using namespace std;
inline int read()
{
    int x=0;char ch=getchar();int t=1;
    for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') t=-1;
    for(;ch>='0'&&ch<='9';ch=getchar()) x=(x<<1)+(x<<3)+(ch-48);
    return x*t;
}
const int N=1e5+100;
struct node{int x;int ti;}a[N];
typedef long long ll;
int id[N];
ll ans[N];
ll anss[N];
int n,m;
node tmp[N];
int tr[2][N];
#define lowbit(a) ((a)&(-a))
inline void add(int p,int x,int k){while(p<=n) tr[k][p]+=x,p+=lowbit(p);}
inline int query(int p,int k) {int res=0;while(p) res+=tr[k][p],p-=lowbit(p);return res;}
inline void CDQ(int l,int r)
{
    if(l==r) return ;
    register int mid=l+r>>1;
    CDQ(l,mid);CDQ(mid+1,r);
    register int p=l,q=mid+1,head=l;register int res;
    for(register int i=l;i<=mid;i++) add(a[i].ti,1,0);
    while(p<=mid&&q<=r)
    {
        if(a[p].x<a[q].x){
            res=query(a[p].ti,1);
            ans[a[p].x]+=res;
            add(a[p].ti,-1,0);
            tmp[head++]=a[p++];
        }
        else{
            res=query(a[q].ti,0);
            ans[a[q].x]+=res;
            add(a[q].ti,1,1);
            tmp[head++]=a[q++];
        }
    }
    while(p<=mid) {add(a[p].ti,-1,0);ans[a[p].x]+=query(a[p].ti,1);tmp[head++]=a[p++];}
    for(register int i=mid+1;i<q;i++) add(a[i].ti,-1,1);
    while(q<=r) tmp[head++]=a[q++];
    for(register int i=l;i<=r;i++) a[i]=tmp[i];
}
int main()
{
    n=read();m=read();
    for(register int i=1;i<=n;i++) a[i].x=read(),a[i].ti=0,id[a[i].x]=i;
    for(register int i=1;i<=m;i++) {int x=read();a[id[x]].ti=n-i+1;}
    register int head=n-m;
    for(register int i=1;i<=n;i++) if(a[i].ti==0) a[i].ti=head--;
    CDQ(1,n);
    for(register int i=1;i<=n;i++) anss[a[i].ti]+=ans[a[i].x];
    for(register int i=1;i<=n;i++) anss[i]+=anss[i-1];//差分
    for(register int i=n;i>=n-m+1;i--) printf("%lld\n",anss[i]);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值