let's start with a simple question----
传说中的逆序对
第一种方法是归并排序顺便求
int merge(int l,int r)
{
int mid=(l+r)>>1,zz1=l,zz2=mid+1,sum=0;
for(int i=l;i<=r;i++){
if(zz2>r||zz1<=mid&&c[zz1]<c[zz2]) cur[i]=c[zz1++];
else{
cur[i]=c[zz2++];
sum+=mid-zz1+1;
}
}
for(int i=l;i<=r;i++) c[i]=cur[i];
return sum;
}
void solve(int l,int r){
if(l<r){
int mid=(l+r)>>1;
solve(l,mid);
solve(mid+1,r);
ans+=merge(l,r);
}
}
二是树状数组
#include<bits/stdc++.h>
#define N 100005
using namespace std;
int c[N],n,ans;
int read(){
int cnt=0;char ch=0;
while(!isdigit(ch))ch=getchar();
while(isdigit(ch))cnt=cnt*10+(ch-'0'),ch=getchar();
return cnt;
}
int lowbit(int x){return x&-x;}
int add(int pos,int val){
for(;pos<=n;pos+=lowbit(pos))
c[pos]+=val;
}
int getsum(int x){
int cnt=0;
for(;x;x-=lowbit(x))cnt+=c[x];
return cnt;
}
int main(){
n=read();
for(int i=1;i<=n;i++){
int a=read();
add(a,1);
ans+=i-getsum(a);
}
cout<<ans;
return 0;
}
将值作为下标存入树状数组,来一个查询比它小的个数,减一下就是比它大的即为逆序对
于是我们知道
树状数组跟分治在某些方面有联系
好,正式进入CDQ分治
求满足Ai<=Aj,Bi<=Bj,Ci<=Cj的数对的数目。
我们先按a排序
我们在先只考虑b
相当于求b的顺序对
再在(a,b)顺序对中找c的顺序对
b可以跟刚刚逆序对一样
c可以用树状数组
看代码吧
int lowbit(int x){return x&-x;}
void add(int pos,int val){
for(;pos<=k;pos+=lowbit(pos)) c[pos]+=val;
}
int quary(int x){
int cnt=0;
while(x) cnt+=c[x],x-=lowbit(x);
return cnt;
}
void cdq(int l,int r){
if(l==r) return;
int mid=(l+r)>>1;
cdq(l,mid),cdq(mid+1,r);
int i=l,j=mid+1;
//先跑个逆序对之类的东西
for(int k=l;k<=r;k++){
if(j>r||(i<=mid&&a[i].y<=a[j].y)) add(a[i].z,a[i].cnt),b[k]=a[i++];
else a[j].ans+=quary(a[j].z),b[k]=a[j++];
}
for(int i=l;i<=mid;i++) add(a[i].z,-a[i].cnt);//清空树状数组
for(int i=l;i<=r;i++) a[i]=b[i];
}
升级版----
维护一个W*W的矩阵,初始值均为S.每次操作可以增加某格子的权值,或询问某子矩阵的总权值.修改操作数M<=160000,询问数Q<=10000,W<=2000000.
第一行两个整数,S,W;其中S为矩阵初始值;W为矩阵大小
接下来每行为一下三种输入之一(不包含引号):
"1 x y a"
"2 x1 y1 x2 y2"
"3"
输入1:你需要把(x,y)(第x行第y列)的格子权值增加a
输入2:你需要求出以左上角为(x1,y1),右下角为(x2,y2)的矩阵内所有格子的权值和,并输出
输入3:表示输入结束
所有cdq分治问题
基本上都要转成前缀和处理
例如这道题(x1,y1)-(x2,y2)相当于mmap[i-1][j-1]+mmap[k][l]-mmap[i-1][l]-mmap[j-1][k]
我们将问题和修改一起放进去,然后按x排序,然后分治
先看代码吧
#include<bits/stdc++.h>
#define N 200005
using namespace std;
struct Node{int x,y,z,pos,id;}a[N],b[N];
int ans[10005],c[N*4],w,s,n,cnt;
int read(){
int cnt=0;char ch=0;
while(!isdigit(ch)) ch=getchar();
while(isdigit(ch))cnt=cnt*10+(ch-'0'),ch=getchar();
return cnt;
}
bool cmp(Node a,Node b){
if(a.x==b.x){
if(a.y==b.y) return a.pos<b.pos;
return a.y<b.y;
}
return a.x<b.x;
}
int lowbit(int x){return x&-x;}
void add(int x,int val){
for(;x<=w;x+=lowbit(x)) c[x]+=val;
}
int quary(int x){
int q=0;
for(;x;x-=lowbit(x)) q+=c[x];
return q;
}
void cdq(int l,int r){
if(l==r) return;
int mid=(l+r)>>1,i=l,j=mid+1;
for(int k=l;k<=r;k++){
if(a[k].id<=mid&&a[k].pos==0) add(a[k].y,a[k].z);//为修改
if(a[k].id>mid&&a[k].pos) ans[a[k].pos]+=quary(a[k].y)*a[k].z;
}
for(int k=l;k<=r;k++)
if(a[k].id<=mid&&a[k].pos==0) add(a[k].y,-a[k].z);
for(int k=l;k<=r;k++){
if(a[k].id<=mid) b[i++]=a[k];
else b[j++]=a[k];
}
for(int k=l;k<=r;k++) a[k]=b[k];
cdq(l,mid),cdq(mid+1,r);
}
int main()
{
//freopen("1.in","r",stdin);
s=read(),w=read();
while(1){
int op=read();
if(op==1){
int x=read(),y=read(),z=read();
a[++n]=(Node){x,y,z,0,n};
}
else if(op==2){
int x1=read()-1,y1=read()-1,x2=read(),y2=read();
ans[++cnt]=(x2-x1)*(y2-y1)*s;
a[++n]=(Node){x1,y1,1,cnt,n};
a[++n]=(Node){x1,y2,-1,cnt,n};
a[++n]=(Node){x2,y1,-1,cnt,n};
a[++n]=(Node){x2,y2,1,cnt,n};
}
else break;
}
sort(a+1,a+n+1,cmp);
cdq(1,n);
for(int i=1;i<=cnt;i++) cout<<ans[i]<<endl;
return 0;
}
前一半的修改一定对后一半的查询有影响
我们相当于找(x,y,time)的顺序对
总得来说
cdq分治一般与树状数组相配合
cdq分治能保证查询一定在修改之后
一维排序,二维分治,三维树状数组