【BZOJ3295】【Cqoi2011】动态逆序对

Description

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

Input

输入第一行包含两个整数nm,即初始元素的个数和删除的元素个数。以下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


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

HINT

N<=100000 M<=50000

题解

cdq分治经典题。

先记下所有询问,然后从后往前递推答案,也就是把删除变为加入,对于每一个加入位置,分别用两次CDQ分治计算出它左面和它右面的答案,再用树状数组记录逆序对。

为什么这样做就可以呢?由于有点得删除,所以对于同一个点在不同时间的逆序对数不同。也就是说时间影响逆序对数,按照每个点的删除顺序把点从n到1标号,在cdq分治的时候枚举一个中间时间mid,把数列分成两部分,因为两部分的编号是递增的,时间是打乱的,所以可以这样做,这样的话左边的数一定比右边的数晚删,就算左边对右边的贡献,分两部分:左边的数在右边的前面并比右边的大,左边的数在右边的后面的比右边的小,,就这样分治下去就行了,最后就可以求出每个点在它所在的时间内产生的逆序对数了。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
const int maxn=100010;
int n,m,t[maxn];
ll lans[maxn],rans[maxn],ans[maxn];
struct node{
	int id,x,tim;
}a[maxn],b[maxn];
int pos[maxn];
void add(int x,int y){
	for(int i=x;i<=n;i+=i&(-i))
	t[i]+=y;
}
int query(int x){
	int ans=0;
	for(int i=x;i>=1;i-=i&(-i))
	ans+=t[i];
	return ans;
}
void cdq(int l,int r){
	if(l>=r) return ;
	int mid=(l+r)>>1,l1=l,l2=mid+1,now=l;
	for(int i=l;i<=r;i++){
		if(a[i].tim<=mid) b[l1++]=a[i];
		else b[l2++]=a[i];
	}
	for(int i=l;i<=r;i++) a[i]=b[i];//只算左边对右边的影响,因为右边先被删。 
	for(int i=mid+1;i<=r;i++){
		for(;now<=mid&&a[i].id>a[now].id;now++) add(a[now].x,1);
		lans[a[i].tim]+=now-l-query(a[i].x);//不用+1因为for之后now又加了1; 
	}
	for(int i=l;i<now;i++) add(a[i].x,-1);
	now=mid;
	for(int i=r;i>mid;i--){
		for(;now>=l&&a[i].id<a[now].id;now--) add(a[now].x,1);
		rans[a[i].tim]+=query(a[i].x-1);
	}
	for(int i=now+1;i<=mid;i++) add(a[i].x,-1);
	cdq(l,mid);cdq(mid+1,r);
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i].x);
		a[i].id=i;
		pos[a[i].x]=i;
	}
	int cnt=n,x;
	for(int i=1;i<=m;i++){
		scanf("%d",&x);
		a[pos[x]].tim=cnt--;
	}
	for(int i=1;i<=n;i++) if(!a[i].tim) a[i].tim=cnt--;
	cdq(1,n);
	for(int i=1;i<=n;i++) ans[i]=ans[i-1]+lans[i]+rans[i];
	for(int i=n;i>n-m;i--) printf("%lld\n",ans[i]); 
	return 0; 
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值