离线分治:整体二分与CDQ分治
离线分治:整体二分与CDQ分治
这两个算法都是离线的分治算法。其中CDQ分治是基于时间的分治算法。整体二分是基于值域的分治算法。先讲讲整体二分吧。
我们拿[ZJOI2013]K大数查询作为例子。
一、原理:将所有的修改和查询操作离线存下来。每次二分所有修改和询问操作,分成两部分解决。
二、每个子问题:slove(front,last,l,r)表示在front到last的操作中,答案在l到r中找。要求给每个question找到答案。
三、实现过程:我们要考虑这些添加操作对询问的影响。在添加操作时,我们把添加到的有影响的地方(即大于mid);在询问时,查找询问区间比mid大的数量。接下来我们的目标是把所有操作归两类继续递归。操作值不大于mid的添加和找到比mid大的数大于k的询问放在第一类(这里k-=cur),其余的放在第二类。然后递归即可。
四、细节错误:
- 递归结束条件:
if (front>last||l>r) return;
。 - 不开LL见祖宗。
- len1,len2不能开全局,会变的。
五、代码实现:(线段树部分略。)
long long cur[50005];
inline void solve(int front,int last,long long l,long long r)
{
if (front>last||l>r) return;
if (l==r)
{
for (Rint i=front;i<=last;i++)
{
if (q[i].t==2) ans[q[i].id]=l;
}
return;
}
long long mid=l+r>>1;
for (Rint i=front;i<=last;i++)
{
if (q[i].t==1&&q[i].c>mid) change(q[i].l,q[i].r,1,1);
else if (q[i].t==2) cur[i]=ask(q[i].l,q[i].r,1);
}
for (Rint i=front;i<=last;i++)
{
if (q[i].t==1&&q[i].c>mid) change(q[i].l,q[i].r,1,-1);
}
int len1=0,len2=0;
for (Rint i=front;i<=last;i++)
{
if (q[i].t==1)
{
if (q[i].c<=mid) q1[++len1]=q[i];
else q2[++len2]=q[i];
}
else
{
if (q[i].c<=cur[i]) q2[++len2]=q[i];
else q[i].c-=cur[i],q1[++len1]=q[i];
}
}
int cnt=front-1;
for (Rint i=1;i<=len1;i++) q[++cnt]=q1[i];
for (Rint i=1;i<=len2;i++) q[++cnt]=q2[i];
solve(front,front+len1-1,l,mid);
solve(front+len1,last,mid+1,r);
}
六、练习
[国家集训队]矩阵乘法 把初始值看做是添加操作,用二维树状数组维护即可。
接下来讲讲CDQ分治。
我们拿【模板】三维偏序(陌上花开)举例子。这道题还有其他做法,如各种树套树和KD-Tree。但是今天讲的是CDQ分治。
零、前置:二维偏序/归并排序/树状数组(或线段树)
P.S.作者此处使用树状数组进行叙述。个人推荐使用树状数组而不是线段树。
我们先不着急考虑三维,先看看二维是怎么做的。对第一个关键字排序,然后求第二个关键字的顺序对就好了。
一、分析
我们仍然为第一关键字排序,第二第三。这样第一维都满足左边小于右边了。
我们考虑每一个子问题。我们把每个子问题分成两部分进行计算,左边和右边都按照为第一关键字归并排序。因为我们要计算的是这个子问题的总答案,所以说两个小子问题的答案我们已经得到了。现在就是要算出跨越两端之间的贡献。很显然,因为排过序,因此左边任何一个一定小于右边任何一个。我们在归并排序的时候计算它的贡献。左指针为,右指针为。若,那么可以为以后的做贡献,把加入树状数组;否则,在以后的都无法为贡献了,那么计算的总贡献,即的前缀和。
树状数组在这里起到的作用是单点修改,区间询问。因此很明显树状数组比线段树方便的多。
二、细节
CDQ分治在此题中还有个问题。对于两个完全一样的东西,我们需要把它们合并。
三、代码
bool cmp2(point x,point y)
{
if (x.y==y.y) return x.z<y.z;
return x.y<y.y;
}
void cdq(int l,int r)
{
if (l==r) return;
int mid=(l+r)>>1;
cdq(l,mid),cdq(mid+1,r);
sort(b+l,b+mid+1,cmp2);
sort(b+mid+1,b+r+1,cmp2);
int j=l;
for (int i=mid+1;i<=r;i++)
{
while (b[i].y>=b[j].y&&j<=mid)
{
add(b[j].z,b[j].cnt);
j++;
}
b[i].ans+=ask(b[i].z);
}
for (int i=l;i<j;i++) add(b[i].z,-b[i].cnt);
}
四、例题
我们先考虑每次查询时左下角的部分。现在可以看出有三个要求:时间,x和y。这三个值都比询问小的修改是有用的。这样就可以三维偏序了。这里要解决的是的最大值,用树状数组维护即可。最后把地图翻转每次cdq就可以了。
这题时间卡常,所以加一些优化,比如不用重新排序,从备用数组里复制就可以。再卡卡常就过了。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 张高兴的大模型开发实战:(一)使用 Selenium 进行网页爬虫
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构