动态逆序对[CDQ]

CDQ二层试炼:

bzoj3295

http://www.lydsy.com/JudgeOnline/problem.php?id=3295


题意大致是:

给你一个序列(一个1~n的排列),每次删掉一个数,求删掉之前的逆序对。


方案1:强行模拟,每次删掉一个一个数,在剩下的数里面用归并求逆序对,复杂度O(mnlogn)

这个复杂度明显是不能接受的


方案2:树套树,代码量令人伤心。


方案3:cdq分治。


之前已经写过一篇浅谈CDQ的了,那到底什么是CDQ呢?

我认为CDQ是一种思想,一种分治的思想。在三维偏序中CDQ的思想就是一维排序,二维分治,三维树状数组。

在这道题里面,我们可以把这道题看成是求(x,y,z)这个点被删除后对总答案的影响。

我们把 当前位置i作为x,把i位置的数的大小作为y ,把删除时间作为z(初始为n||无穷大,就是对答案无影响)

接下来考虑 删去(x,y,z)对答案有什么影响。  首先一点最容易想到,它只会影响到zi>z的点(之前的点都已经被删去,那些点不会影响到逆序对的个数).

于是,我们就可以对z这一维排序。(我是从大到小排的,这样处理比较方便)

这样,删去一个点只会对在它左边的点的某些点构成逆序对的减少。

在zi>z的情况下接着考虑删去(x,y,z)的影响:

如果xi>x&&yi

如果xiy(这个点在当前点前面&&这个点的值>当前点的值)构成一个逆序对,删去之后逆序对-1

接下来怎么做?

z这一维已经排好序了,我们仍然进行分治。

(下面以分治x,数据结构维护y为例。              PS:我的代码是分治y,数据结构维护x,大家可以推一下,熟悉一下)

考虑[l,mid],[mid+1,r]的2分区间,  分别将按x进行两次排序.

然后考虑左区间对右区间的影响:

(这里是求删去当前点对答案的影响)

①按x递减排序,  枚举右区间j,在左区间一直更新 i.x>j.x的i,将i.y在树状数组中的值+1(逆序对数+1),然后查询[1,j.y)这个区间的和,把它累加到当前点的影响中。

按x递增排序,  枚举右区间的j,在左区间一直更新 i.x


然后就做完了.

分治的时候先调用左区间,再调用右区间,然后计算左区间对右区间的影响。(其实先后顺序可以交换,习惯而已)


然后建议自己推一下分治y,维护x的情况(我的代码也是这种)


然后写这一篇文章的目的是加深对CDQ的理解,but我光荣的调了好久的bug,从WA成T,最后改了一下分治的sort,优化了常数才过的!


附上AC代码:

#include
#include
#include
#include
#include
#include
#include
#define LL long long
using namespace std;
const LL maxn=100000+20;
LL n,m;
struct node
{
	LL x,y,z;//数的位置x,大小y,删除时间z 
}q[maxn],r1[maxn],r2[maxn];
LL shan[maxn];
bool cmpy(node a,node b)
{
	return a.yb.z;
	if(a.y!=b.y)return a.y>b.y;
	return a.xb.y;
}
bool cmpx(node a,node b)
{
	return a.x>b.x;
}
long long fl[maxn],fr[maxn];
/*
恒保证 左区间z >右区间 删除右区间某个值, 对答案的贡献是 
对于一个点(x,y,z)删掉它减少的逆序对是左区间 zi>z :
xi>x yiy 记为fl 
*/ 
long long f[maxn];
void updata(LL i)
{
	while(i<=n)
	{
		f[i]++;
		i+=(i&-i);
	}
}
void clear(LL i)
{
	while(i<=n)
	{
		f[i]=0;
		i+=(i&-i);
	}
}
LL query(LL i)
{
	LL ans=0;
	while(i)
	{
		ans+=f[i];
		i-=(i&-i);
	}
	return ans;
}
void work(LL l,LL r)
{
	LL mid=(l+r)>>1;
	int rt1,rt2;
	rt1=rt2=0;
	for(int i=l;i<=mid;i++)r1[++rt1]=q[i];
	for(int i=mid+1;i<=r;i++)r2[++rt2]=q[i];
	sort(r1+1,r1+rt1+1,cmpy2);
	sort(r2+1,r2+rt2+1,cmpy2);
	//计算所有yi>yj 的xir2[j].y;i++)updata(r1[i].x);
		fl[r2[j].y]+=query(r2[j].x);
	} 
	for(LL i=1;i<=rt1;i++)clear(r1[i].x);
	//计算所有xi>x yi=1;j--)
	{
		for(;i>=1&&r1[i].yq[j].x;i++)updata(q[i].y);
		fr[q[j].y]+=query(q[j].y);
	}
	for(LL i=l;i<=mid;i++)clear(q[i].y);
	sort(q+l,q+r+1,cmpz);
	*/
}
void cal(LL l,LL r)
{
	if(l==r)return ;
	LL mid=(l+r)>>1;
	cal(l,mid);
	cal(mid+1,r);
	work(l,r);
}
long long sum=0;
long long t[maxn];
long long a[maxn],b[maxn];
long long cao[maxn]; 
void guibing(LL l,LL r)
{
	if(l==r)
	{
		cao[l]=t[l];
		return ;
	}
	LL mid=(l+r)>>1;
	LL t1,t2;
	t1=t2=0;
	guibing(l,mid);
	for(LL i=l;i<=mid;i++)a[i]=cao[i];
	guibing(mid+1,r);
	for(LL i=mid+1;i<=r;i++)b[i]=cao[i];
	LL i=l,j=mid+1;
	LL tt=l;
	while(i<=mid&&j<=r)
	{
		if(a[i]>b[j])
		{
			sum+=mid-i+1;
			cao[tt++]=b[j++];
		}
		else cao[tt++]=a[i++];
	}
	while(i<=mid)cao[tt++]=a[i++];
	while(j<=r)cao[tt++]=b[j++];
}
int main()
{
	scanf("%lld%lld",&n,&m);
	for(LL i=1;i<=n;i++)
	{
		q[i].x=i;
		scanf("%lld",&q[i].y);
		q[i].z=n;
		t[i]=q[i].y;
	}
	sort(q+1,q+n+1,cmpy);
	for(LL i=1;i<=m;i++)
	{
		LL x;
		scanf("%lld",&x);
		q[x].z=i;
		shan[i]=x;
	}
	sort(q+1,q+n+1,cmpz);
	cal(1,n);
	//
	guibing(1,n);
	for(LL i=1;i<=m;i++)
	{
		printf("%lld\n",sum);
		sum-=fl[shan[i]]+fr[shan[i]];
	}
	return 0;
}




你可能感兴趣的:(数据结构,三维,树状数组,分治,cdq,bzoj)