CDQ分治,顾名思义,这个思想重点在于如何 分 治 。
分:将问题从整体分解成几个小部分(一般是两个)。
治:考虑各部分之间的贡献并计算,把部分合并到整体。
二维偏序模板问题:
一个平面上,给出n个点(X,Y)。
定义: 设 A,B 两点,如果A.x
求每个点的权值。
最方便的解决办法当然是树状数组,对其中一维(x)排序,另一维(y)在以x的顺序插入树状数组中时,查询答案即可。
这里不累述。
还有一种方法就是CDQ分治:
附上自己写的伪代码:
void CDQ(int l,int r)
{
if(r-l<1) return; // 结束条件
/*
分
预处理排序x;
此时二分处理时左右区间左区间x全部小于右区间的x
所以右区间不会对左区间产生贡献
*/
int m = (l+r)>>1; // 二分处理
CDQ(l,m); // 递归处理左边界
CDQ(m+1,r); // 递归处理右边界
/*
治
合并区间时,由于递归处理,此时左右两部分的y是有序的 见下
而且在分的时候x是有序的
所以只有左区间会对右区间产生贡献
我们便可以在线性时间内处理左区间对右区间的贡献并以y排好序
*/
int p = l , q = m+1 , t = 0;
while(q<=r || p<=m){
/*
指针移动法
处理左区间对右区间的贡献;
并把排好序的结构体数组付给中间数组temp
*/
}
t = 0;
for(int i=l;i<=r;i++) // 整体排序 所以说上面合并时已经有序
z[i] = temp[++t];
}
给出例题 : HDU 1541
在刚刚二维偏序的基础上我们给定n个点加入平面的顺序,每个点只考虑当前时间的点对它产生贡献,不考虑后加的点。
这时,应该扩充平面的点一个时间元素t,设定第一次加入点时t=1,往后时间t增加,一直到n。
这时A对B产生贡献,不仅需要A.x
这便是一个三维偏序问题
那么我们如何处理三维偏序
我们已经知道 分治+排序 与 树状数组+排序 可以解决二维偏序问题, 排序一维+分治一维 = 二维 , 排序一维 + 树状数组一维 = 二维 ,那我们能否 排序一维 + CDQ一维 + 树状数组一维 = 三维 ?
不可以
答案当然是可以
我大致讲一下: 首先排序使x有序,然后进行CDQ分治,其基本还是和二维偏序时操作一样,但是在整体合并的处理上,不是直接查找,而是以y的顺序插入到树状数组当中,然后以t查找。
这是大致思维
更高维的偏序问题我未接触过,但是应该和我上述一样,升维即可,树套树,CDQ套CDQ等等…
#include
#define ll long long
using namespace std;
const int maxn=1e5+5, maxm=5e4+5;
int n,m;
struct node
{
int va,pos,t;
};
node z[maxn],tem[maxn];
int C[maxn];
int lowbit(int x)
{
return x&-x;
}
void add(int x,int d)
{
while(x<=n)
{
C[x]+=d, x+=lowbit(x);
}
}
int sum(int x)
{
int ans = 0;
while(x)
{
ans+=C[x], x-=lowbit(x);
}
return ans;
}
ll ans[maxn];
void CDQ(int l,int r)
{
if(l>=r)
return;
int mid = (l+r)>>1;
int q,p,k;
p = l, q = mid+1;
for(int i=l; i<=r; i++) // 根据t的大小把点二分为左区间与右区间
if(z[i].t<=mid)
tem[p++] = z[i];
else
tem[q++] = z[i];
for(int i=l; i<=r; i++)
z[i] = tem[i];
p = l, q = mid+1, k = 0; // 正向求贡献
while(p<=mid || q<=r)
{
if(q>r || p<=mid&&z[p].pos<z[q].pos)
{
add( z[p].va, 1 ) ;
k++ ;
p++;
}
else
{
ans[z[q].t] += (k - sum(z[q].va)) ;
q++;
}
}
for(int i=l; i<=mid; i++) // 清除树状数组
add(z[i].va,-1);
p = mid, q = r; // 反向求贡献
while(p>=l || q>mid)
{
if(q<=mid || p>=l&&z[p].pos>z[q].pos)
{
add(z[p].va, 1) ;
p-- ;
}
else
{
ans[z[q].t] += sum(z[q].va) ;
q-- ;
}
}
for(int i=l; i<=mid; i++) // 清除树状数组
add(z[i].va,-1);
CDQ(l,mid);
CDQ(mid+1,r);
}
int a[maxn],ma[maxn];
int main()
{
ios::sync_with_stdio(false);
cin>>n>>m;
for(int i=1; i<=n; i++)
cin>>a[i];
for(int i=1; i<=m; i++)
{
int b;
cin>>b;
ma[b] = i;
}
int k = 0;
for(int i=1; i<=n; i++)
{
int b = ma[a[i]];
if(b)
{
z[i] = node{a[i],i,n-b+1};
}
else
z[i] = node{a[i],i,++k};
}
CDQ(1,n);
for(int i=2; i<=n; i++)
ans[i] += ans[i-1];
for(int i=n; i>n-m; i--)
cout<<ans[i]<<endl;
}
不想拿金的Acmer不是好的Acmer