update at 2022/10/28,cdq+二维数点板子
1.首先是结构体,一个node就是一个数点,b用来存数点,s是临时数组,cdq的时候会用到
//ty=1表示实际点,ty=2表示询问的虚点,v表示权值(实际点权值是1,虚点权值是询问权值)
struct node{int ty,x,y,v,id;};
node b[N*5], s[N*5];
2.然后是cmp函数,一般是按照{x,ty}二元组从小到大排序
bool cmp(const node&a,const node&b){
if(a.x==b.x) return a.ty<b.ty; //如果x相同,ty小的放前面(因为x相等时实点能对询问贡献)
else return a.x<b.x; //否则就按x排序,x小的放前面
}
3.然后是add_point和add_query,要注意point的ty=1,query的ty=2,而具体函数怎么写要看题目要求,这个不是固定的。
void addP(int x,int y,int v){ //x,y是坐标,v是点的权值(本题权值都是1)
b[++cnt] = {1,x,y,v,0};
}
void addQ(int x1,int y1,int x2,int y2,int id){ //id表示第id个询问
b[++cnt] = {2,x2,y2,1,id};
b[++cnt] = {2,x1-1,y2,-1,id};
b[++cnt] = {2,x2,y1-1,-1,id};
b[++cnt] = {2,x1-1,y1-1,1,id};
}
4.最后是cdq函数,大部分内容固定,统计答案部分要改
void cdq(int l,int r){
if(l==r) return;
int mid=l+r>>1;
cdq(l,mid); cdq(mid+1,r);
int pos = mid+1, sum=0, copy=l-1;
FOR(i,l,mid){
while(pos<=r && b[pos].y < b[i].y){
s[++copy] = b[pos]; //归并排序正常操作(固定)
//这一步,条件固定,但是后面的内容可能改
if(b[pos].ty==2) ans[b[pos].id] += b[pos].v*sum;
pos++;
}
if(b[i].ty==1) sum+=b[i].v; //累计左边贡献(固定)
s[++copy] = b[i]; //归并排序正常操作(固定)
}
while(pos<=r){ //再把右半部分剩下的统计答案并且放入s数组
s[++copy] = b[pos]; //(固定)
//统计答案,条件固定,后面的可能改
if(b[pos].ty==2) ans[b[pos].id] += b[pos].v*sum;
pos++;
}
memcpy(b+l,s+l,sizeof(node)*(r-l+1)); //数组复制
}
1.cdq分治的好处:不需要离散化
2.二维数点可以封装成两个函数,add_point和add_query。其中addP是二维上的单点修改,addQ是二维上一个矩形区域的询问。
#include <bits/stdc++.h>
using namespace std;
#define FOR(i, a, b) for (int i = (a); i <= (b); ++i)
const int N = 6e5+10;
int n,m; //n个点,m个修改/询问
struct node{int ty,x,y,v,id;}; //ty=1表示实际点,ty=2表示询问的虚点,v表示权值(实际点权值是1,虚点权值是询问权值)
node a[N*5], s[N*5];
int cnt, ans[N];
bool cmp(const node&a,const node&b){
if(a.x==b.x) return a.ty<b.ty; //如果x相同,ty小的放前面(因为x相等时实点能对询问贡献)
else return a.x<b.x; //否则就按x排序,x小的放前面
}
void addP(int x,int y,int v){ //x,y是坐标,v是点的权值(本题权值都是1)
a[++cnt] = {1,x,y,v,0};
}
void addQ(int x1,int y1,int x2,int y2,int id){ //id表示第id个询问
a[++cnt] = {2,x2,y2,1,id};
a[++cnt] = {2,x1-1,y2,-1,id};
a[++cnt] = {2,x2,y1-1,-1,id};
a[++cnt] = {2,x1-1,y1-1,1,id};
}
void cdq(int l,int r){
if(l==r) return; //处理完了,结束
int mid=l+r>>1;
cdq(l,mid); cdq(mid+1,r);
int pos = mid+1, sum=0, copy=l-1;
FOR(i,l,mid){
while(pos<=r && a[pos].y < a[i].y){
s[++copy] = a[pos]; //归并排序正常操作
if(a[pos].ty==2) ans[a[pos].id] += a[pos].v*sum; //[l,i-1]部分能对a[pos]贡献,从i开始不能了,所以统计答案
pos++;
}
if(a[i].ty==1) sum+=a[i].v; //累计贡献
s[++copy] = a[i]; //归并排序正常操作
}
while(pos<=r){ //再把右半部分剩下的统计答案并且放入s数组
s[++copy] = a[pos];
if(a[pos].ty==2) ans[a[pos].id] += a[pos].v*sum; //统计答案
pos++;
}
memcpy(a+l,s+l,sizeof(node)*(r-l+1)); //数组复制
}
void solve(){
cin>>n>>m;
FOR(i,1,n){
int x,y; cin>>x>>y;
addP(x,y,1);
}
FOR(i,1,m){
int x1,y1,x2,y2; cin>>x1>>y1>>x2>>y2; //左下角和右上角
addQ(x1,y1,x2,y2,i);
}
sort(a+1,a+cnt+1,cmp); //二维数点按x排序
cdq(1,cnt); //cdq分治处理
FOR(i,1,m) cout<<ans[i]<<'\n';
}
signed main(){
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
int T=1; //cin>>T;
while(T--) solve();
}
当然,有很多其他问题也可以转化成二维数点问题,从而用cdq+二维数点解决,比如:
【1】单点修改,区间询问问题,比如这个:
#include <bits/stdc++.h>
using namespace std;
#define FOR(i, a, b) for (int i = (a); i <= (b); ++i)
#define int long long
const int N = 5e5+10;
int n,m; //n个点,m个修改/询问
struct node{int ty,x,y,v,id;}; //ty=1表示实际点,ty=2表示询问的虚点,v表示权值(实际点权值是1,虚点权值是询问权值)
node a[N*5], s[N*5];
int cnt=0, ans[N];
int tot=0;
bool cmp(const node&a,const node&b){
if(a.x==b.x) return a.ty<b.ty; //如果x相同,ty小的放前面(因为x相等时实点能对询问贡献)
else return a.x<b.x; //否则就按x排序,x小的放前面
}
void addP(int x,int y,int v){ //x,y是坐标,v是点的权值(本题权值都是1)
a[++cnt] = {1,x,y,v,0};
}
void addQ(int x1,int y1,int x2,int y2,int id){ //id表示第id个询问
a[++cnt] = {2,x2,y2,1,id};
a[++cnt] = {2,x1-1,y2,-1,id};
a[++cnt] = {2,x2,y1-1,-1,id};
a[++cnt] = {2,x1-1,y1-1,1,id};
}
void cdq(int l,int r){
if(l==r) return; //处理完了,结束
int mid=l+r>>1;
cdq(l,mid); cdq(mid+1,r);
int pos = mid+1, sum=0, copy=l-1;
FOR(i,l,mid){
while(pos<=r && a[pos].y < a[i].y){
s[++copy] = a[pos]; //归并排序正常操作
if(a[pos].ty==2) ans[a[pos].id] += a[pos].v*sum; //[l,i-1]部分能对a[pos]贡献,从i开始不能了,所以统计答案
// if(ans[a[pos].id]>0) cout<<a[pos].id<<' '<<ans[a[pos].id]<<endl;
pos++;
}
if(a[i].ty==1) sum+=a[i].v; //累计贡献
s[++copy] = a[i]; //归并排序正常操作
}
while(pos<=r){ //再把右半部分剩下的统计答案并且放入s数组
s[++copy] = a[pos];
if(a[pos].ty==2) ans[a[pos].id] += a[pos].v*sum; //统计答案
pos++;
}
memcpy(a+l,s+l,sizeof(node)*(r-l+1)); //数组复制
}
void print(int x){
cout<<a[x].ty<<' '<<a[x].x<<' '<<a[x].y<<' '<<a[x].v<<endl;
}
void solve(){
cin>>n>>m;
FOR(i,1,n){
int x; cin>>x;
addP(i,i,x); //一维是时间,一维是位置,权值是x
}
FOR(i,n+1,n+m){
int ty,x,y; cin>>ty>>x>>y;
if(ty==1) addP(i,x,y); //修改
else if(ty==2) addQ(1,x,i,y,++tot); //询问
}
sort(a+1,a+cnt+1,cmp);
cdq(1,cnt);
FOR(i,1,tot) cout<<ans[i]<<'\n';
}
signed main(){
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
int T=1; //cin>>T;
while(T--) solve();
}
【2】逆序对问题(待更...)