CDQ分治+树状数组[动态逆序对]

Description

对于序列A,它的逆序对数定义为满足i<j,且Ai>Aj的数对(i,j)的个数。给1到n的一个排列,按照某种顺序依次删

除m个元素,你的任务是在每次删除一个元素之前统计整个序列的逆序对数

Input

输入第一行包含两个整数n和m,即初始元素的个数和删除的元素个数。

以下n行每行包含一个1到n之间的正整数,即初始排列。

以下m行每行一个正整数,依次为每次删除的元素。

N<=100000 M<=50000

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

Source

黑暗爆炸3295

分析
将删除看做倒着插入,记录三元组{time,pos,val}

time表示第几个插入,很明显第一个删除的time为n

pos表示在原队列中的位置

val表示值

我们按time排序,对于当前time点插入的元素j,有贡献的元素i为

条件1:time_i<time_j

条件2:pos_i>pos_j && val_i<val_j

或者 pos_i<pos_j && val_i>val_j

于是就是3维偏序问题了
读入数据之后先按照time为第一关键字,pos为第二关键字排序,然后cdq分治,第三维用树状数组统计

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
const int maxn=1e5+5;
struct node{
	int pos,val,t;//position,value,time
}a[maxn],b[maxn];
int n,m,Time,id[maxn],treearray[maxn]; 
ll ans[maxn];
int camp(struct node a,struct node b){
	if(a.t!=b.t)
	    return a.t<b.t;
	return  a.pos<b.pos;
} 
void add(int locate,int wight){//树状数组插入 
	while(locate<=n){
		treearray[locate]+=wight;
		locate+=locate&(-locate);
	}
	return;
}
int quary(int locate){//树状数组欻查询 
	int ans=0;
	while(locate>0){
		ans+=treearray[locate];
		locate-=locate&(-locate);
	}
	return ans;
}
void cdq(int left,int right){
	int mid=(left+right)>>1;
	if(left==right)
	    return;
	cdq(left,mid);
	cdq(mid+1,right);
	int i=left,j=mid+1;//处理pos_i<pos_j 且 val_i>val_j的
	for(int k=left;k<=right;k++){//这里就是限制一下一共进行r-l+1次操作 
	    if((i<=mid&&a[i].pos<a[j].pos)||j>right){//我们得到的两个区间,左区间的任何T都小于右区间的任何T,左右区间分别已经按照pos排好序了 
			add(a[i].val,1);//这里就是找到了符合pos_i<pos_j 且 val_i>val_j的,会对右区间产生贡献扔进树状数组 
			b[k]=a[i];//这里是为了合并两个区间的时候按照pos排序,这样合并只要O(n),如果排序要O(nlogn) 
			i++;
		}
		else{
			ans[a[j].t]+=quary(n)-quary(a[j].val);//这里代表此j要被合并进大区间了,这时候计算一下val_i>val_j的总和个数 
			b[k]=a[j];
			j++;
		}
	}
	for(int i=left;i<=mid;i++)
		add(a[i].val,-1);
//下面的两种写法任取一种就可以 
/*----------------------------------------------法一 就是先合并出大区间再按照time计算,因为合并之后time无序 
	for(int i=left;i<=right;i++)
		a[i]=b[i];
	for(int i=right;i>=left;i--){
		if(a[i].t<=mid)
		    add(a[i].val,1);
		else
		    ans[a[i].t]+=quary(a[i].val);
	}
	for(int i=left;i<=right;i++)
	    if(a[i].t<=mid)
	        add(a[i].val,-1);
*/

/*----------------------------------------------法二 就是按照处理pos_i<pos_j 且 val_i>val_j的思路再算一下 pos_i>pos_j 且 val_i<val_j的
    	i=mid;j=right;
	for(int k=left;k<=right;k++){
	   if((i>=left&&a[i].pos>a[j].pos)||j<mid+1){
	    	add(a[i].val,1);
	    	i--;
     	}
	else{
	    	ans[a[j].t]+=quary(a[j].val-1);
	    	j--;
    	}
	}
	for(int i=left;i<=mid;i++)
		add(a[i].val,-1);
	for(int i=left;i<=right;i++)//记得合并两个区间,而且要按照 处理pos_i<pos_j 且 val_i>val_j的b[]数组合并 
		a[i]=b[i];
*/
	return;
}
int main()
{
	scanf("%d %d",&n,&m);
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i].val);
		a[i].pos=i;
		id[a[i].val]=i;//id[n]表示val值为n的元素所在原序列的位置 
	}
	Time=n;
	for(int i=1;i<=m;i++){
		int k;
		scanf("%d",&k);
		a[id[k]].t=Time--;
	}
	for(int i=1;i<=n;i++)//为了保证剩下的数可以产生原序列对应的逆序对贡献,要从1-n而不是n-1 
		if(a[i].t==0)
		    a[i].t=Time--;
	sort(a+1,a+n+1,camp);//先按照time排序再按照pos排序 
	cdq(1,n);
	for(int i=1;i<=n;i++)
		ans[i]+=ans[i-1];//ans计算的是每个time点贡献的逆序对,要求time时刻对应的序列的逆序对就要累加前缀 
	for(int i=1;i<=m;i++)
		printf("%lld\n",ans[n-i+1]);//int会WA 
  return 0;
}

2021.10.3

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值