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
分析
将删除看做倒着插入,记录三元组{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