引述
序列中,对于每个i,统计满足
a[j]<a[i]
的j的数量,我们可以将序列按a排序来解决
若状态扩展到二维,对于每个二元组(a[i],b[i])求满足
a[j]<a[i],b[j]<b[i]
的j的数量,则可以将所有二元组按a排序,按下标从小到大扫描序列,依次将元素插入树状数组,再用BIT查询此点前缀和即可
但是若每次操作插入一个元素,或者询问一个元素,那么时间也会成为两个操作互相影响的变量,故不能随意排序了,这时我们就需要CDQ分治来对这个三维问题求解
做法
我们可以先将操作序列的时间t记录下来,然后按照a排序,之后便有两种不同的做法
做法1-快排法
每次分治到的区间[l,r],可以保证所有的元素时间也在[l,r]内。令mid=(l+r)/2,按a升序扫描区间,依次将其中所有时间小于mid的修改和时间大于mid的查询在树状数组上完成,这样事先的排序保证了a的顺序,分治解决了t的顺序,树状数组则解决了b的。然而这里只处理了小于mid的修改对大于mid的询问的影响,故还要将区间按时间划分为[l,mid]和[mid+1,r]两段,递归处理。
回顾以上过程,我们每次分治都把区间以mid为标准分为左右两半,然后对左右进行递归,这不正是快速排序的过程吗?因此我们将它命名为快排法
做法2-归并法
既然CDQ的实质是在将数组排序的过程中累计答案,那么除了快排有没有其他支持分治的排序方式呢?答案是肯定的。
分治排序与快排不同,要先递归左右两半,后对整个区间进行归并。这样,在递归过后,整个区间分为左右两部分,每一部分内部按t排好序(由排序性质),左面的任意一个a小于右面任意一个a。于是我们可以在左面和右面分别设置指针,按照类似归并的方法,处理左面的修改对右面询问的影响,只要保证按时间顺序操作即可。最后在将区间二路归并,保证其排序性质。
与快排法相比,这种方法不需要严格保证t和区间下标范围相同,但是归并细节较多,可能在代码量上有较大差距。
实现
其实CDQ分治能解决各种三维影响问题,不要局限于时间t。但是若选择方法1,则必须有一维做为时间(即值域为1~n)保证分治区间吻合。
实现上有一点需要注意,每次分治都需要一个空的树状数组来维护答案,但是每次都清空数组会使用O(
n2
)的时间。所以我们在每次递归前都把本次所有修改操作反向做一遍,这样的时间效率是O(nlog
2
n)。
这里只给出分治部分的代码,结构体声明、排序和树状数组请读者自行完成
快排法代码
void cdq(int l=1,int r=cnto)
{
int p1=l;
int p2=mid+1;
for(int i=l;i<=r;i++)
{
if(o[i].t<=mid&&!o[i].qid)
bit.add(o[i].b,1);
if(o[i].t>mid&&o[i].qid)
ans[o[i].qid]+=bit.getsum(o[i].b);
}
for(int i=l;i<=r;i++)
if(o[i].t<=mid&&!o[i].qid)
bit.add(o[i].b,-1);
for(int i=l;i<=r;i++)
if(o[i].t<=mid) aux[p1++]=o[i];
else aux[p2++]=o[i];
for(int i=l;i<=r;i++) o[i]=aux[i];
if(l<mid)cdq(l,mid);
if(mid+1<r)cdq(mid+1,r);
}
归并法代码
声明:此份代码非博主本人完成,仅供参考和理解
void solve(int l,int r)
{
int mid;
int f1;
int f2;
if(l==r)
return;
mid=(l+r)>>1;
solve(l,mid);
solve(mid+1,r);
for(f1=l,f2=mid+1;(f1<=mid)&&(f2<=r);)
if(szp[f1].x<=szp[f2].x)
{
if(szp[f1].l==0)
adds(szp[f1].y,szp[f1].d);
f1++;
}
else
{
if(szp[f2].l!=0)
sza[szp[f2].d]+=gets(szp[f2].y)*szp[f2].l;
f2++;
}
for(;f2<=r;f2++)
if(szp[f2].l!=0)
sza[szp[f2].d]+=gets(szp[f2].y)*szp[f2].l;
for(f1--;f1>=l;f1--)
if(szp[f1].l==0)
adds(szp[f1].y,-szp[f1].d);
totn=l;
for(f1=l,f2=mid+1;(f1<=mid)&&(f2<=r);)
if(szp[f1].x<=szp[f2].x)
szn[totn++]=szp[f1++];
else
szn[totn++]=szp[f2++];
for(;f1<=mid;f1++)
szn[totn++]=szp[f1];
for(;f2<=r;f2++)
szn[totn++]=szp[f2];
for(f1=l;f1<=r;f1++)
szp[f1]=szn[f1];
return;
}
完结撒花