BZOJ3295 || 洛谷P3157 [CQOI2011]动态逆序对【CDQ分治】

Time Limit: 10 Sec
Memory Limit: 128 MB

Description

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

Input

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

Output

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


题目分析

将删除元素改为插入元素
每个元素以一个三元组 ( t , x , y ) (t,x,y) (t,x,y)表示
其中属性t代表插入时刻 x x x代表序列位置 y y y代表权值

对于第 i i i个删除的元素,将他的属性t赋值为 t = n − i + 1 t=n-i+1 t=ni+1
剩下没有删除的元素的属性t,用 1 1 1~ n − i n-i ni任意对应一个就好了

a n s [ t ] ans[t] ans[t]表示插入 t t t时刻对应的元素增加了多少逆序对
那么通过ans的前缀和就可以得出答案了

关键的问题在于求解 a n s [ t ] ans[t] ans[t]
不难想到插入 ( t i , x i , y i ) (t_i,x_i,y_i) (ti,xi,yi)后增加的逆序对数量
就是满足 ( t j &lt; t i , x j &lt; x i , y j &gt; y i ) (t_j&lt;t_i,x_j&lt;x_i,y_j&gt;y_i) (tj<ti,xj<xi,yj>yi) ( t j &lt; t i , x j &gt; x i , y j &lt; y i ) (t_j&lt;t_i,x_j&gt;x_i,y_j&lt;y_i) (tj<ti,xj>xi,yj<yi) j j j的数量
到这里就转化成了可以用CDQ分治求解的三维偏序问题


#include<iostream>
#include<vector>
#include<algorithm>
#include<queue>
#include<cstring>
#include<cstdio>
using namespace std;
typedef long long lt;
#define lowbit(x) ((x)&(-x)) 

int read()
{
    int f=1,x=0;
    char ss=getchar();
    while(ss<'0'||ss>'9'){if(ss=='-')f=-1;ss=getchar();}
    while(ss>='0'&&ss<='9'){x=x*10+ss-'0';ss=getchar();}
    return x*f;
}

const int maxn=200010;
int n,m,cnt;
int pos[maxn];
struct node{int t,x,y;}a[maxn],b[maxn];
bool cmp(node a,node b){return a.t<b.t;}
lt tree[maxn],ans[maxn];

void add(int x,int v){ for(int i=x;i<=n;i+=lowbit(i))tree[i]+=v;}
lt qsum(int x){ lt res=0; for(int i=x;i>0;i-=lowbit(i))res+=tree[i]; return res;}

void CDQ(int ll,int rr)
{
    if(ll==rr) return;
    int mid=ll+rr>>1;
    CDQ(ll,mid); CDQ(mid+1,rr);
    
    int t1=ll,t2=mid+1,p=ll;
    while(t2<=rr)//先求满足(t_j<t_i, x_j<x_i, y_j>y_i)的j的数量
    {
        while(a[t1].x<a[t2].x&&t1<=mid) 
        add(a[t1].y,1),b[p++]=a[t1++];
        ans[a[t2].t]+=qsum(n)-qsum(a[t2].y); b[p++]=a[t2++]; 
    }
    for(int i=ll;i<t1;++i) 
    add(a[i].y,-1);
    
    while(t1<=mid) b[p++]=a[t1++];
    while(t2<=rr) b[p++]=a[t2++];
    for(int i=ll;i<=rr;++i) a[i]=b[i];
    
    for(int i=rr;i>=ll;--i)//上面xi已经被归并升序排序,所以直接倒叙遍历一次即可
    if(a[i].t<=mid) add(a[i].y,1);//注意if判断,必须是左子区间对右子区间产生贡献
    else ans[a[i].t]+=qsum(a[i].y);
    for(int i=ll;i<=rr;++i) if(a[i].t<=mid) add(a[i].y,-1);
}

int main()
{
    n=cnt=read();m=read();
    for(int i=1;i<=n;++i)
    {
    	int x=read(); pos[x]=i;
    	a[i].t=0; a[i].x=i; a[i].y=x;
    }
    for(int i=1;i<=m;++i) a[pos[read()]].t=cnt--;
    for(int i=1;i<=n;++i) if(!a[i].t) a[i].t=cnt--;
    
    sort(a+1,a+1+n,cmp);//第一维直接按t升序排序
    CDQ(1,n);
    for(int i=1;i<=n;++i) ans[i]+=ans[i-1];
    for(int i=n;i>=n-m+1;--i) printf("%lld\n",ans[i]);
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值