题面
本文讲cdq,整体二分的思路与做法。=分治VS数据结构
其实维度这一方面,空间几何可以是维度,像时间这样有规定顺序的词语也可能是维度。
cdq
三维偏序,一般可以用一维一维的消。可以用cdq嵌套,线段树嵌套来做,我还不会,这里用排序一维,cdq一维,树状数组一维,感觉对比嵌套在思维拓展方面有帮助。
题面的\(f[i]\)满足\(a[j]\leq a[i],b[j]\leq b[i],c[j]\leq c[i]\),因为元素相同的情况在分治中会出现漏解,所以cdq分治在出现相同元素时常做去重处理。
做法:
先按去重后序列以a为第一关键字 注意不是唯一关键字 排序,
然后常规分治,之后将左右区间分别按照b为(第一)关键字排序,
这时因为我们要根据左区间给右区间贡献,而左区间的a值一定都小于右区间的a值,所以a无影响。按照b归并排序的同时更新树状数组,并得到右区间的贡献最后记得清空树状数组
代码
#include<cstdio>
#include<iostream>
#include<algorithm>
#define M (l+r>>1)
using namespace std;
const int N=1e5+5;
int n,k,tot,ans[N],tr[N<<1];
struct qh{
int a,b,c,ans,cnt;
}in[N],a[N];
bool c1(qh a,qh b){return a.a==b.a?a.b==b.b?a.c<b.c:a.b<b.b:a.a<b.a;}
bool c2(qh a,qh b){return a.b==b.b?a.c<b.c:a.b<b.b;}
int lowbit(int x){return x&(-x);}
void add(int x,int y){for(int i=x;i<=k;i+=lowbit(i)) tr[i]+=y;return ;}
int gs(int x){int res=0;for(int i=x;i;i-=lowbit(i)) res+=tr[i];return res;}
void cdq(int l,int r){
if(l==r) return ;
cdq(l,M);cdq(M+1,r);
sort(a+l,a+M+1,c2);sort(a+M+1,a+r+1,c2);
int q1=l,q2=M+1;
while (q1<=M&&q2<=r){
if(a[q1].b<=a[q2].b){
add(a[q1].c,a[q1].cnt);
q1++;
}
else{
a[q2].ans+=gs(a[q2].c);
q2++;
}
}
while (q2<=r){
a[q2].ans+=gs(a[q2].c);
q2++;
}
for(int i=l;i<q1;i++) add(a[i].c,-a[i].cnt); //*******
return ;
}
int main(){
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++) scanf("%d%d%d",&in[i].a,&in[i].b,&in[i].c);
sort(in+1,in+n+1,c1);
int c=0;
for(int i=1;i<=n;i++){
c++;
if(in[i].a!=in[i+1].a||in[i].b!=in[i+1].b||in[i].c!=in[i+1].c) a[++tot]=(qh){in[i].a,in[i].b,in[i].c,0,c},c=0;
}
cdq(1,tot);
for(int i=1;i<=tot;i++) ans[a[i].ans+a[i].cnt-1]+=a[i].cnt;
for(int i=0;i<n;i++) printf("%d\n",ans[i]);
return 0;
}
//cdq分治:相同元素做去重处理
整体二分
题目大意:
给定一个长度为 n ( n <= 50,000 ) 的数组 a[1] , a[2] ... a[n] 和 q ( q <= 10,000 ) 此询问,每次询问:
-
Q i j k 表示区间 [i,j] 中第 k 小的数是多少,并输出这个数
-
C i t 表示将第 i 个数改为 t
样例输入
5 3
3 2 1 4 7
Q 1 4 3
C 2 6
Q 2 5 3
样例输出
3
6
整体二分,顾名思义,就是把所有的东西拿来一起二分。在这道题里我们还要开一棵树状数组。
- 修改操作等于删除+添加
- 查询操作=二分答案
- 修改和加入按照所在位置二分
- 顺序处理所有操作
如果哪一步不懂就看代码吧。
#include<cstdio>
#include<iostream>
#define M (l+r>>1)
using namespace std;
const int N=5e4+5;
int n,q,tot,qtot,a[N],ans[N],tr[N],cnt[N<<1];
struct qh{
int x,y,z,t,ad,s;
}E[N<<1],El[N<<1],Er[N<<1],op;
inline int Rd(){
int s=0,w=1;char ch=getchar();
while (ch<'0'||ch>'9'){if(ch=='-') w=-1;ch=getchar();}
while (ch>='0'&&ch<='9') s=(s<<1)+(s<<3)+ch-'0',ch=getchar();
return s*w;
}
inline int lowbit(int x){return x&(-x);}
inline void add(int x,int y){
for(int i=x;i<=n;i+=lowbit(i)) tr[i]+=y;
return ;
}
inline int gs(int x){
int res=0;
for(int i=x;i;i-=lowbit(i)) res+=tr[i];
return res;
}
inline void init(int h,int t,int l,int r){
if(h>t) return ;
if(l==r){ //pd l==r
for(int i=h;i<=t;i++) if(E[i].t==3) ans[E[i].ad]=l;
return ;
}
for(int i=h;i<=t;i++){ //ls
if(E[i].t==1&&E[i].y<=M) add(E[i].x,1);
else if(E[i].t==2&&E[i].y<=M) add(E[i].x,-1);
else if(E[i].t==3) cnt[i]=gs(E[i].y)-gs(E[i].x-1);
}
for(int i=h;i<=t;i++){ //empty
if(E[i].t==1&&E[i].y<=M) add(E[i].x,-1);
else if(E[i].t==2&&E[i].y<=M) add(E[i].x,1);
}
int l1=0,r1=0;
for(int i=h;i<=t;i++){ //merge
if(E[i].t==3){
if(E[i].s+cnt[i]>=E[i].z) El[l1++]=E[i];
else E[i].s+=cnt[i],Er[r1++]=E[i];
}
else if(E[i].y<=M) El[l1++]=E[i];
else Er[r1++]=E[i];
}
for(int i=0;i<l1;i++) E[h+i]=El[i];
for(int i=0;i<r1;i++) E[h+l1+i]=Er[i];
init(h,h+l1-1,l,M);
init(h+l1,t,M+1,r);
}
int main(){
n=Rd();q=Rd();
for(int i=1;i<=n;i++){
a[i]=Rd();
op.x=i;op.y=a[i];op.t=1;
E[++tot]=op;
}
while (q--){
char ch=getchar();
int x,y,z;
while (ch!='Q'&&ch!='C') ch=getchar();
if(ch=='Q'){
x=Rd();y=Rd();z=Rd();
op.x=x;op.y=y;op.z=z;op.t=3;op.ad=++qtot;
E[++tot]=op;
}
else{
x=Rd();y=Rd();
op.x=x;op.y=a[x];op.t=2;E[++tot]=op;
op.x=x;op.y=y;op.t=1;E[++tot]=op;
a[x]=y;
}
}
init(0,tot,0,1e9);
for(int i=1;i<=qtot;i++) printf("%d\n",ans[i]);
return 0;
}
结构跟权值线段树还挺像
总结
整体二分的 思想 是同时对处理区间和答案进行二分。
CDQ分治的 **思想 **是用处理方式(关键字)进行排序,然后对时间进行二分。
整体二分 可以用于 求询问操作一样,而且可以二分答案解决的问题(具有单调性,如时间可以二分)。
CDQ分治 可以用于 求多维偏序问题。
整体二分从上往下做,CDQ分治从下往上做。
两种分治算法都比较暴力,它们的优点是代码短而清晰,缺点是复杂度玄学,必须离线。
所以,这次还是没能决出胜负啊。
标签:偏序,二分,int,分治,陌上,CDQ,include,cdq From: https://www.cnblogs.com/Tonvia/p/16732297.html