[Cqoi2011]动态逆序对
题意
对于序列A,它的逆序对数定义为满足i<j,且Ai>Aj的数对(i,j)的个数。给1到n的一个排列,按照某种顺序依次删除m个元素,你的任务是在每次删除一个元素之前统计整个序列的逆序对数。
分析
参照broxin的题解。
这本质上是一个三维偏序,分别是时间,下标,数值,记为(t,x,y)。
我们可以把删除的过程倒过来,当做插入来做,时间t表示这个数是第几个插入的,显然给出的删除的点的t值依次是N,N-1,N-2...(越先删除的视为越后插入的)注意不在询问范围内的点的t值可以任意设置,并且显然没有哪两个点有相同的t或x或y值,这使得问题好考虑得多了。我们求的就是按顺序插入每一个数时,这个数左边比它大的、右边比它小的分别有多少个。形式化地,对一个点(t0,x0,y0),求出满足t<t0,x<x0,y>y0的点的个数记为lda[t0],满足t<t0,x>x0,y>y0的点的个数记为rxiao[t0]。
我想了一会儿,觉得最外层按x排比较科学,内部对t进行划分排序(相当于快排,将t值<=mid的划分到左边,同时对于划分到同一侧的点要保证原来的相对顺序不变),对y用树状数组来维护。每个节点[L,R]划分出来是这样的:
要找[L,mid]对[mid+1,R]的贡献:
先考虑对lda的贡献。枚举t∈[mid+1,R]的点(t0,x0,y0),区间内的点由于已经按时间划分好了,所以不需要考虑t<t0这一条件。只需找出左区间中x<x0且y>y0的点,由于两边的x值各自保持单调(如图),所以可以像树状数组求逆序对一样,将[L,mid]区间内的点的y值在树状数组上增加1,然后求[mid+1,R]的每个y值在树状数组上的前缀和即可。由于l1和l2都是单增的,这一操作复杂度为nlogn。
再考虑对rxiao的贡献:类似地,找出[L,mid]中x值大于[mid+1,R]中的x值的即可。
注意一层分治并不能找出[mid+1,R]中所有值的lda和rxiao,但整个分治一定会不遗漏不重复地覆盖每个点的决策区间。每层复杂度nlogn,总共logn层,总复杂度nlog^2n。貌似有人说cdq分治可以做到nlogn?我觉得不太科学,毕竟三维,不可能把某一维直接吃掉吧。。
cdq分治做这种题真是优秀,空间复杂度仅为O(n),时间复杂度也不逊于高级数据结构,并且分治(对半分)以及树状数组的常数都是极小的,基本是严格logn。
快排式的cdq,学习了。
代码
#include<bits/stdc++.h>
#define rg register
#define il inline
#define co const
#define lowbit(x) (x&-x)
template<class T>il T read(){
rg T data=0,w=1;
rg char ch=getchar();
while(!isdigit(ch)){
if(ch=='-') w=-1;
ch=getchar();
}
while(isdigit(ch))
data=data*10+ch-'0',ch=getchar();
return data*w;
}
template<class T>il T read(rg T&x){
return x=read<T>();
}
typedef long long ll;
co int N=1e5+1;
int n,m;
namespace T{
int c[N];
void add(int p,int v){
for(int i=p;i<=n;i+=lowbit(i))
c[i]+=v;
}
int sum(int p){
int re=0;
for(int i=p;i;i-=lowbit(i))
re+=c[i];
return re;
}
}
struct dot{
int t,x,y; // every t,x,y is unique
}a[N],np[N];
int lda[N],rxiao[N];
ll ans[N];
void cdq(int L,int R){
using namespace T;
if(L>=R) return;
int mid=(L+R)/2;
int l1=L,l2=mid+1;
for(int i=L;i<=R;++i){
if(a[i].t<=mid) np[l1++]=a[i];
else np[l2++]=a[i];
}
std::copy(np+L,np+R+1,a+L);
l1=L;
for(int i=mid+1;i<=R;++i) // smaller x, bigger y
{
for(;l1<=mid&&np[l1].x<np[i].x;++l1)
add(np[l1].y,1);
lda[np[i].t]+=(l1-L)-sum(np[i].y);
}
for(int i=L;i<l1;++i)
add(np[i].y,-1);
l1=mid;
for(int i=R;i>mid;--i){
for(;l1>=L&&np[l1].x>np[i].x;--l1)
add(np[l1].y,1);
rxiao[np[i].t]+=sum(np[i].y-1);
}
for(int i=l1+1;i<=mid;++i)
add(np[i].y,-1);
cdq(L,mid),cdq(mid+1,R);
}
int pos[N];
int main(){
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
read(n),read(m);
for(int i=1;i<=n;++i)
read(a[i].y),a[i].x=i,pos[a[i].y]=i;
int t,tmr=n;
for(int i=1;i<=m;++i)
read(t),a[pos[t]].t=tmr--;
for(int i=1;i<=n;++i) if(!a[i].t)
a[i].t=tmr--;
cdq(1,n);
for(int i=1;i<=n;++i)
ans[i]=ans[i-1]+rxiao[i]+lda[i];
for(int i=n;i>n-m;--i)
printf("%lld\n",ans[i]);
return 0;
}