动态逆序对[CDQ分治]

题目描述

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

输入格式:

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

输出格式:

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

输入样例#1: 

5 4
1
5
3
4
2
5
1
4
2

输出样例#1: 

5
2
2
1

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

说明

N<=100000 M<=50000


分析

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

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

pos表示在原队列中的位置

val表示值

我们按time排序,发现对当前time的答案有贡献的为

time_i<time

pos_i>pos && val_i<val

或者 pos_i<pos && val_i>val

于是就是3维偏序问题了


#include<bits/stdc++.h>
#define N 200005
#define LL long long
using namespace std;
struct Node{int t,x,y;}q[N],tmp[N];
int n,m,Time,a[N],match[N],c[N];
LL ans[N];
bool cmp(Node a,Node b){return a.t<b.t;}
int read(){
	int cnt=0;char ch=0;
	while(!isdigit(ch))ch=getchar();
	while(isdigit(ch))cnt=cnt*10+(ch-'0'),ch=getchar();
	return cnt;
}
void add(int x,int val){for(;x<=n;x+=x&-x) c[x]+=val;}
int quary(int x){
	int ans=0;
	for(;x;x-=x&-x) ans+=c[x];
	return ans;
}
void CDQ(int l,int r){
	if(l==r) return;
	int mid=(l+r)>>1;
	CDQ(l,mid),CDQ(mid+1,r);
	int i=l,j=mid+1;
    //处理pos_i<pos 且 val_i>val的
	for(int k=l;k<=r;k++){
		if(j>r||(i<=mid&&q[i].x<q[j].x)) add(q[i].y,1),tmp[k]=q[i++];
		else ans[q[j].t]+=quary(n)-quary(q[j].y),tmp[k]=q[j++];
	}
	for(int k=l;k<=mid;k++) add(q[k].y,-1);
	for(int k=l;k<=r;k++) q[k]=tmp[k];
    //处理pos_i>pos && val_i<val 的贡献 (已经按x排好序)
	for(int i=r;i>=l;i--){
		if(q[i].t<=mid) add(q[i].y,1);
		else ans[q[i].t]+=quary(q[i].y); 
	}
	for(int i=l;i<=r;i++)
		if(q[i].t<=mid) add(q[i].y,-1);
}
int main(){
	n=read(),m=read(); Time=n;
	for(int i=1;i<=n;i++){
		a[i]=read();
		q[i].t=0 , q[i].x=i , q[i].y=a[i];
		match[q[i].y]=i;
	}
	for(int i=1;i<=m;i++){
		q[match[read()]].t=Time--;
	}
	for(int i=1;i<=n;i++)
		if(q[i].t==0) q[i].t=Time--;
	sort(q+1,q+n+1,cmp);
	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;
}

猜你喜欢

转载自blog.csdn.net/sslz_fsy/article/details/83245649