离线分治:整体二分与CDQ分治

离线分治:整体二分与CDQ分治

这两个算法都是离线的分治算法。其中CDQ分治是基于时间的分治算法。整体二分是基于值域的分治算法。先讲讲整体二分吧。


我们拿[ZJOI2013]K大数查询作为例子。

一、原理:将所有的修改和查询操作离线存下来。每次二分所有修改和询问操作,分成两部分解决。

二、每个子问题:slove(front,last,l,r)表示在front到last的操作中,答案在l到r中找。要求给每个question找到答案。

三、实现过程:我们要考虑这些添加操作对询问的影响。在添加操作时,我们把添加到的有影响的地方(即大于mid);在询问时,查找询问区间比mid大的数量。接下来我们的目标是把所有操作归两类继续递归。操作值不大于mid的添加和找到比mid大的数大于k的询问放在第一类(这里k-=cur),其余的放在第二类。然后递归即可。

四、细节错误:

  1. 递归结束条件:if (front>last||l>r) return;
  2. 不开LL见祖宗。
  3. 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.作者此处使用树状数组进行叙述。个人推荐使用树状数组而不是线段树。

我们先不着急考虑三维,先看看二维是怎么做的。对第一个关键字排序,然后求第二个关键字的顺序对就好了。

一、分析

我们仍然x为第一关键字排序,y第二z第三。这样第一维都满足左边小于右边了。

我们考虑每一个子问题。我们把每个子问题分成两部分进行计算,左边和右边都按照b为第一关键字归并排序。因为我们要计算的是这个子问题的总答案,所以说两个小子问题的答案我们已经得到了。现在就是要算出跨越两端之间的贡献。很显然,因为a排过序,因此左边任何一个a一定小于右边任何一个a。我们在归并排序的时候计算它的贡献。左指针为j,右指针为i。若bj<bi,那么j可以为以后的做贡献,把cj加入树状数组;否则,在j以后的j都无法为i贡献了,那么计算i的总贡献,即ci的前缀和。

树状数组在这里起到的作用是单点修改,区间询问。因此很明显树状数组比线段树方便的多。

二、细节

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);
}

四、例题

P4169 [Violet]天使玩偶/SJY摆棋子

我们先考虑每次查询时左下角的部分。现在可以看出有三个要求:时间,x和y。这三个值都比询问小的修改是有用的。这样就可以三维偏序了。这里要解决的是xj+yj的最大值,用树状数组维护即可。最后把地图翻转每次cdq就可以了。

这题时间卡常,所以加一些优化,比如不用重新排序,从备用数组里复制就可以。再卡卡常就过了。

posted @   Little09  阅读(143)  评论(0编辑  收藏  举报
编辑推荐:
· .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语句:使用策略模式优化代码结构
点击右上角即可分享
微信分享提示