分治 2
CDQ分治&整体二分
CDQ 分治
CDQ 分治可以解决的问题有三维偏序和离线带修询问
三维偏序
首先看一下二维偏序怎么做,
通常是离线扫描一维,数据结构统计另一维
在此基础上,我们处理第三个维度。
首先对于离线扫描这一维,lin4xu 学长教过所谓 “离线分治和扫描线”
就是离线之后要么分治,要么扫描线,
因为这两个都是很方便降低维度的
这里会发现扫描线的性质并不是很好,
因为扫描的顺序固定一个维度排个序,然后会发现剩下的两个下标怎么办?
要么是再扫一遍,要么用一个二维的数据结构,于是寄了
所以为什么不两个一起用呢?
首先对 \(A\) 排序然后分治,然后在分治的时候处理左对右的贡献
这样的话处理左对右的数据就是二维偏序了
点击查看代码
using namespace std;
#define int long long
const int o=2222222,inf=1e18;
int n,Ans[o],A[o],B[o],Id[o];
struct ques{
int op,t,a,b,id;
}q[o],Q[o];
struct SgT{
struct node{
int val,id;
}t[o];
void up(int p){
if(t[p*2].val!=inf)t[p].val=t[p*2].val,t[p].id=t[p*2].id;
else t[p].val=t[p*2+1].val,t[p].id=t[p*2+1].id;
}
void build(int p,int L,int R){
if(L==R)return void(t[p].val=inf);
int mid=(L+R)/2;
build(p*2,L,mid);
build(p*2+1,mid+1,R);
up(p);
}
void change(int p,int L,int R,int pos,int val,int id){
if(L==R){
// cerr<<"change:"<<t[p].val<<" "<<L<<" "<<R<<" "<<val<<" "<<id<<"\n";
if(val<t[p].val||val==inf){
t[p].val=val;
t[p].id=id;
}
return;
}
int mid=(L+R)/2;
if(pos<=mid)change(p*2,L,mid,pos,val,id);
else change(p*2+1,mid+1,R,pos,val,id);
up(p);
}
int ques(int p,int L,int R,int l,int r){
if(l<=L&&R<=r)return t[p].id;
int mid=(L+R)/2,ans=0;
if(l<=mid)ans=ques(p*2,L,mid,l,r);
if(ans==0&&r>mid)ans=ques(p*2+1,mid+1,R,l,r);
return ans;
}
}T;
bool cmp(ques a,ques b){return a.a==b.a?a.b<b.b:a.a<b.a;}
bool cmpb(ques a,ques b){return a.b==b.b?a.a<b.a:a.b<b.b;}
bool cmpt(ques a,ques b){return a.t<b.t;}
int amin(int x,int y,int t){
if(x==t||x==0)return y;
if(!y)return x;
if(cmpb(Q[Id[x]],Q[Id[y]]))return x;
return y;
}
void solve(int L,int R){
if(L==R)return;
int mid=(L+R)/2;
solve(L,mid);
solve(mid+1,R);
sort(q+L,q+mid+1,cmp);
sort(q+mid+1,q+R+1,cmp);
int l=mid;
vector<int>pos;
for(int r=R;r>mid;r--){
if(q[r].op==0)continue;//add
int now=T.ques(1,1,n,q[r].b,n);
Ans[q[r].t]=amin(now,Ans[q[r].t],q[r].id);
while(l>=L&&q[l].a>q[r].a){
if(q[l].op==0){//add
pos.push_back(l);
T.change(1,1,n,q[l].b,q[l].a,q[l].id);
}
l--;
int now=T.ques(1,1,n,q[r].b,n);
Ans[q[r].t]=amin(now,Ans[q[r].t],q[r].id);
}
while(l>=L&&q[l].a==q[r].a&&q[l].b>q[r].b){
if(q[l].op==0){//add
pos.push_back(l);
T.change(1,1,n,q[l].b,q[l].a,q[l].id);
}
l--;
int now=T.ques(1,1,n,q[r].b,n);
Ans[q[r].t]=amin(now,Ans[q[r].t],q[r].id);
}
}
for(auto i:pos)T.change(1,1,n,q[i].b,inf,0);
}
void work(){
cin>>n;
int cnt=0;
T.build(1,1,n);
for(int i=1;i<=n;i++){
char op;
cin>>op;
if(op=='D'){
q[i].id=++cnt;
cin>>q[i].a>>q[i].b;
Id[cnt]=i;
}
else{
int x;cin>>x;
q[i].a=q[Id[x]].a,q[i].b=q[Id[x]].b;
q[i].op=1,q[i].id=x;
}
q[i].t=i;
}
for(int i=1;i<=n;i++)A[i]=q[i].a,B[i]=q[i].b;
sort(A+1,A+n+1),sort(B+1,B+n+1);
A[0]=unique(A+1,A+n+1)-A-1,B[0]=unique(B+1,B+n+1)-B-1;
for(int i=1;i<=n;i++){
q[i].a=lower_bound(A+1,A+A[0]+1,q[i].a)-A;
q[i].b=lower_bound(B+1,B+B[0]+1,q[i].b)-B;
Q[i]=q[i];
}
solve(1,n);
sort(q+1,q+n+1,cmpt);
for(int i=1;i<=n;i++){
if(q[i].op==0)continue;
if(Ans[i]==0)puts("NE");
else printf("%lld\n",Ans[i]);
}
}
#undef int
int main(){
work();
return 0;
}
为什么处理每层的时候是正确的呢?
因为一开始排序是按照第一维排好了的,
选取某个分界点左边和右边分别打乱不会影响到跨分界点两边的偏序关系
所以我们在分治处理跨中点的贡献只需要额外对第二维排序和双指针就行了
需要注意的是,数据结构在递归返回的时候需要做好清空工作
带修问题
考虑一个修改什么时候会对查询做出贡献
很显然是在“修改发生在查询之前”的修改才会影响查询
所以把这个自己作为第三维偏序就行了
优化 DP
这个优化 DP 常见于一些毒瘤斜率优化里
张老师说这个可以李超线段树通解,暂且放下不表
当然还有一些纯粹的三维偏序转化
整体二分
整体二分通常是多组询问,发现答案具有单调性
但是多组询问暴力做超时了的
考虑把所有问题放在一起解决,
假设当前的答案值域是 \([L,R]\),待处理的问题集合是 \(Q\)
那么就可以通过验证 \(mid\) 的合法性,将 \(Q\) 划分成两部分递归去做
然后接下来其实是利用数据结构维护一个通用的 check 去降低时间复杂度的过程

浙公网安备 33010602011771号