算法问题描述
CDQ问题的经典实现是偏序问题,即对于给定的 i i 有 且有形如(下面)的条件,问满足条件的 (i,j) ( i , j ) 有几个
算法思路
假如只有一维,我们容易想到直接排序即可,每个
i
i
对它后面的数有贡献1(其实每个收到的贡献就是i,答案直接就是
∑n1
∑
1
n
)。
假如有两维,我们先排序,把
ai
a
i
当做下标,这就是经典的逆序对(顺序对)问题,我们这里只讨论顺序的情况!经典的方法之一是使用树状数组,每个
bi
b
i
对大于它的
bj
b
j
有贡献,哈希记在树状数组里就行了,每次更新完树状数组后查询i位置的值,这就是贡献(因为前面的
b′<bi
b
′
<
b
i
才是
a′<ai
a
′
<
a
i
的)。
但是更常见的方法是使用归并排序
bi
b
i
,用类似的思想,还是先按
a
a
排序每次分治左右区间,回溯时统计左区间对右区间的影响(此时的左右区间是已经排序完了的,归并的过程中进来的是左边则,进来的是右边则
ans+=tot
a
n
s
+
=
t
o
t
,原理同上,因为归并只有每次的左边对右边答案的贡献,所以是不会重复的)
到了三维,我们是不是可以考虑把分治和树状数组联系起来搞一搞呢,一维排序,二维分治,三维树状数组,只是更新最后无脑
tot++
t
o
t
+
+
变成“归并的过程中进来的是左边则更新树状数组,进来的是右边则查询树状数组”
其实也可以只用分治,这种分治的名称就叫做CDQ分治,它的准确定义是用一个子问题去更新另一个子问题,于是它具有嵌套的能力,四五维偏序也可以搞了
画外音
其实很多时候,那种查询+更新二维区间的东西,看起来是两维其实这种东西是三维偏序!因为有一层隐藏的维度是时间,这时候不需要初始化排序了,直接做就行
写这种题要先把题目转化为最经典的模型,n维偏序,看着不等式写CDQ分治!干巴巴地想是想不出来的
不存在代码的
BOI2007Mokia(CDQ+树状数组)
#include <bits/stdc++.h>
#define maxn 2000005
#define MAXN 200005
#define INF 0x3f3f3f3f
using namespace std;
int n,ans[MAXN];
struct QUERY{
int type,x,y,w,aid;
bool operator < (const QUERY& rhs)const{
return x==rhs.x?type<rhs.type:x<rhs.x;
}
}que[MAXN<<2];
int arr[maxn];
void update(int x,int w){
for(;x<=n;x+=x&-x) arr[x]+=w;
}
int query(int x){
int ans=0;
for(;x;x-=x&-x) ans+=arr[x];
return ans;
}
void clear(int x){
for(;x<=n;x+=x&-x) arr[x]=0;
}
QUERY temp[maxn];
void CDQ(int f,int r){
if(r<=f) return ;
int tot=f;
int mid=(f+r)>>1,p=f,q=mid+1;
CDQ(f,mid);
CDQ(mid+1,r);
while(p<=mid && q<=r){
if(que[p]<que[q]){
if(que[p].type==1) update(que[p].y,que[p].w);
temp[tot++]=que[p++];
}
else{
if(que[q].type==2) ans[que[q].aid]+=que[q].w*query(que[q].y);
temp[tot++]=que[q++];
}
}
while(p<=mid){
//if(que[p].type==1) update(que[p].w,1);
temp[tot++]=que[p++];
}
while(q<=r){
if(que[q].type==2) ans[que[q].aid]+=que[q].w*query(que[q].y);
temp[tot++]=que[q++];
}
for(int i=f;i<=r;i++){
if(temp[i].type==1)clear(temp[i].y);
que[i]=temp[i];
}
}
int main(){
int meiyong,type,cnt=0,ppt=0;
scanf("%d%d",&meiyong,&n);
while(1){
scanf("%d",&type);
if(type==1){
int x,y,w;
scanf("%d%d%d",&x,&y,&w);
que[++cnt]=(QUERY){1,x,y,w,0};
}
else if(type==2){
int X1,X2,Y1,Y2;
scanf("%d%d%d%d",&X1,&Y1,&X2,&Y2);
que[++cnt]=(QUERY){2,X2,Y2,1,++ppt};
que[++cnt]=(QUERY){2,X1-1,Y1-1,1,ppt};
que[++cnt]=(QUERY){2,X2,Y1-1,-1,ppt};
que[++cnt]=(QUERY){2,X1-1,Y2,-1,ppt};
}
else break;
}
CDQ(1,cnt);
for(int i=1;i<=ppt;i++) printf("%d\n",ans[i]);
return 0;
}
陌上花开 CDQ套CDQ
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define maxn 100005
#define MAXN 100005
#define LL long long
using namespace std;
int n,m,ans[maxn],res[maxn];
int read(){
int res=0; char c; c=getchar();
while(c<'0' || c>'9') c=getchar();
while(c>='0' && c<='9') {res=res*10+c-48; c=getchar();};
return res;
}
struct Node{
int x,y,z;
int *ans;
bool flag;
inline void get(){
x=read(); y=read(); z=read();
}
bool operator < (const Node& rhs)const{
return (x<rhs.x)||(x==rhs.x&&y<rhs.y)||(x==rhs.x&&y==rhs.y&&z<rhs.z);
}
bool operator ==(const Node&rhs)const{
return x==rhs.x&&y==rhs.y&&z==rhs.z;
}
}a[maxn],b[maxn],c[maxn];
void meger2(int l,int r){
if(l==r) return;
int mid=(l+r)>>1;
meger2(l,mid); meger2(mid+1,r);
for(int p=l,q=mid+1,cnt=l,tot=0;cnt<=r;++cnt){
if((q>r||b[p].z<=b[q].z)&&p<=mid){
c[cnt]=b[p++];
if(c[cnt].flag) tot++;
}
else{
c[cnt]=b[q++];
if(!c[cnt].flag) *c[cnt].ans+=tot;
}
}
for(int i=l;i<=r;i++) b[i]=c[i];
return ;
}
void meger1(int l,int r){
if(l==r) return;
int mid=(l+r)>>1;
meger1(l,mid); meger1(mid+1,r);
for(int p=l,q=mid+1,cnt=l;cnt<=r;++cnt){
if((q>r||a[p].y<=a[q].y)&&p<=mid){
b[cnt]=a[p++];
b[cnt].flag=1;
}
else{
b[cnt]=a[q++];
b[cnt].flag=0;
}
}
for(int i=l;i<=r;i++) a[i]=b[i];
meger2(l,r);
return ;
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i){
a[i].get();
a[i].ans=&ans[i];
}
sort(a+1,a+n+1);
for(int i=n-1;i;i--) if(a[i]==a[i+1]) *a[i].ans=*a[i+1].ans+1;
//%%%luogu大佬
meger1(1,n);
for(int i=1;i<=n;i++) ++res[ans[i]];
for(int i=0;i<n;i++) printf("%d\n",res[i]);
return 0;
}