• [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
    ",ans[i]);
    	return 0;
    }
    
  • 相关阅读:
    类的特殊获取方式——《Thinking in Java》随笔008
    方法排序规范——《Thinking in Java》随笔007
    权限修饰符(访问指示符)——《Thinking in Java》随笔006
    史上最强,万能类型:Object——《Thinking in Java》随笔005
    七天C#小结
    编译原理10 消除左递归
    编译原理9 DFA最小化,语法分析初步
    编译原理8 非确定的自动机NFA确定化为DFA
    编译原理7 正规式、正规文法与自动机
    编译原理6 正规文法与正规式
  • 原文地址:https://www.cnblogs.com/autoint/p/10372633.html
Copyright © 2020-2023  润新知