首页 > 其他分享 >CDQ 分治

CDQ 分治

时间:2025-01-22 17:58:50浏览次数:1  
标签:right int 分治 mid leq CDQ 区间

前置:偏序问题

其实算不上前置,但是本篇是对这篇的补充。

CDQ 分治

如果你学过归并排序,那你肯定知道归并排序的做法是先让前后两部分有序,然后进行合并,这与 CDQ 的思想是差不多的。

CDQ 的整体仍然是分治,递归处理左右区间,但不同的是,CDQ 会考虑左区间对右区间的影响,并对于右区间或者答案进行更改与修正。

举个例子

逆序对

统计 \(i < j\),且 \(a_i > a_j\) 的数对 \((i,j)\) 个数。

\(n \leq 5 \times 10 ^ 5\)

具体来讲,分治时使用 \(i,j\) 分别记录左(\(\left[ l,mid \right]\))、右区间(\(\left[ mid + 1,r \right]\))的当前位置。

当 \(a_i < a_j\) 时,对答案没有贡献,将 \(a_i\) 加入数组 \(b\) 中。

当 \(a_i > a_j\) 时,因为左右区间已经处理过了,所以现在两个区间都是有序的,所以有 \(x \in \left[ i,mid\right],a_x > a_j\)。对答案的贡献为 \(mid - i + 1\)。然后将 \(a_j\) 加入数组 \(b\)。

当遍历完左右区间后,数组 b 为有序数组,直接赋值给原区间即可。

前置中的三位偏序

三位偏序

有 $ n $ 个元素,第 $ i $ 个元素有 $ a_i,b_i,c_i$ 三个属性,设 $ f(i) $ 表示满足 $ a_j \leq a_i $ 且 $ b_j \leq b_i $ 且 $ c_j \leq c_i $ 且 $ j \ne i $ 的 \(j\) 的数量。

对于 $ d \in [0, n) $,求 $ f(i) = d $ 的数量。

$ 1 \leq n \leq 10^5$。

首先排序可以消掉一维,还剩两维。

分治时使用 sort 将左右区间分别排序,又消掉一维。

最后一维使用树状数组,使用 \(i,j\) 分别记录左(\(\left[ l,mid \right]\))、右区间(\(\left[ mid + 1,r \right]\))的当前位置。枚举 \(j\),对于每个 \(i\) 满足 \(b_i < b_j\),将树状数组上 \(c_i\) 的位置 +1,这样在查找 \(c_i \leq c_j\) 时,\(i\) 的个数为树状数组上 \(c_j\) 的前缀和。即为产生的贡献。

代码

#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,m,cnt,t,res[2000005];
struct node{
	int a,b,c,cnt,ans;
	bool operator!=(const node &x)const
	{
		return (a!=x.a || b!=x.b || c!=x.c);
	}
}h[2000005],a[2000005];
bool cmpa(node x,node y)
{
	if(x.a!=y.a) return x.a<y.a;
	if(x.b!=y.b) return x.b<y.b;
	return x.c<y.c; 
}
bool cmpb(node x,node y)
{
	if(x.b!=y.b) return x.b<y.b;
	return x.c<y.c;
}
int c[2000005];
void modify(int x,int k)
{
	for(int i=x;i<=m;i+=(i&-i))
	{
		c[i]+=k;
	}
}
int query(int x)
{
	int ans=0;
	for(int i=x;i>=1;i-=(i&-i))
	{
		ans+=c[i];
	}
	return ans;
}
void CDQ(int l,int r)
{
	if(l==r) return;
	int mid=(l+r)>>1;
	CDQ(l,mid);
	CDQ(mid+1,r);
	sort(a+l,a+mid+1,cmpb);
	sort(a+mid+1,a+r+1,cmpb);
	int i=l,j=mid+1;
	while(j<=r)
	{
		while(i<=mid && a[i].b<=a[j].b)
		{
			modify(a[i].c,a[i].cnt);
			i++;
		}
		a[j].ans+=query(a[j].c);
		j++;
	}
	for(int k=l;k<i;k++) modify(a[k].c,-a[k].cnt); 
}
signed main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	{
		cin>>h[i].a>>h[i].b>>h[i].c;
	}
	sort(h+1,h+1+n,cmpa);
	for(int i=1;i<=n;i++)
	{
		t++;
		if(h[i]!=h[i+1])
		{
			cnt++;
			a[cnt]=h[i];
			a[cnt].cnt=t;
			t=0;
		}
	}
	CDQ(1,cnt);
	for(int i=1;i<=cnt;i++)
	{
		res[a[i].cnt+a[i].ans-1]+=a[i].cnt;
	}
	for(int i=0;i<n;i++)
	{
		cout<<res[i]<<'\n';
	} 
	return 0;
}

标签:right,int,分治,mid,leq,CDQ,区间
From: https://www.cnblogs.com/lichenxi111/p/18686552

相关文章

  • 点分治维护树上修改与查询
    点分治维护树上修改与查询具体方法就是将操作(修改与查询)离线,并打上时间戳,将其挂在点上,这样就可以考虑一个点到另一个点的贡献是否可以在其询问之前到达。对于所有的点分治都要效:避免算到同一个子树中,可以先整体计算后,在分别进入每个子树中,这样就可以不使用动态开点线段树了......
  • 分治优化DP
    分治优化DPLink\(\text{Para.1}\hspace{0.2cm}\)四边形不等式对于形如\(\text{dp}[i][j]=\min_{k<j}{\text{dp}[i-1][k]+\text{cost}[k+1][j]}\)的形式,若\(\text{cost}\)满足\(\text{cost}[a][c]+\text{cost}[b][d]\leq\text{cost}[a][d]+\text{cost}[b......
  • CDQ 分治 && 整体二分
    CDQ分治主要用于解决偏序问题。在偏序问题中,以三维偏序居多。它是一种离线算法。其实严格来说,它是一种思想而不是算法。它依赖于归并排序。CDQ分治也可以用于1D/1D动态规划的转移,不过目前暂不涉及。偏序问题什么是偏序?先从一维偏序说起。一维偏序给定\(n\)个点,每个点......
  • 分治
    1.解释把一个问题分解成多个不同子问题,当子问题可以迎刃而解时,整个问题也就迎刃而解优点:可以将复杂的问题简单化,让问题更好解决缺点:很容易扣细节就绕进去了,很难使人相信这东西的正确性2.步骤分而治之1.尝试去分解问题(最难的一步)2.不断分解直到没法继续分解3.利用简单......
  • 点分治&&点分树
    点分治前置芝士:树的重心树的重心指对于一棵无根树上的一点,其分割开的若干子树大小的最大值最小。一般用DFS求解树的重心。初始时,\(\mathit{mxs}=\mathit{sum}=n\),即树的节点个数。最终的\(\mathit{rt}\)即为重心。voidgetrt(intu,intfno){ ints=0; siz[u]=1; for(i......
  • 分治的思想
    分治算法是一种很重要的算法策略,它的基本思想是将一个复杂的问题分解成更多相同或相似的子问题,所以分治算法通常以递归的方式实现。就像之前讲的归并排序(不知道可以翻翻之前的博客),将排序的过程不断拆解,排n个数可以排两个n/2个数,看做排n/2再/2,排4段数,最后可以看做排n段长度......
  • 线段树分治-学习笔记
    线段树分治-学习笔记阅前须知:本文给出了线段树分治的一道例题以及多道习题,同时给出了部分实现的代码,帮助学习线段树分治。总述线段树分治是一种离线算法,在于把修改挂在线段树的节点上,通过遍历线段树求出每个叶子节点的答案,以减小复杂度。例题P5787二分图题目大意:\(n\)个点......
  • 点分治
    更新日志2025/01/07:开工。概念点分治,通常用于处理大规模的树上路径信息问题。思路我们将原问题划分为多种,对于每个节点,统计经过这个节点且位于这棵子树内的路径答案。为了缩减复杂度,对于每一棵子树,我们都找到它的重心,以重心为新根在子树内进行操作。找重心示例:intcn......
  • 分治杂记
    分治杂记分治(DivideandConquer),就是把一个复杂的问题分成若干子问题,分别求解。本质是缩小了问题的规模。普通的分治[ABC373G]NoCrossMatching给定平面上的\(n\)个黑点和\(n\)个白点,构造一种方案,将黑白点两两匹配并连线段,使得任意两条线段不相交。\(n\leq100\),保......
  • 深入了解分治 FFT
    问题提出算法应用于问题,分治FFT的出现是为了解决这样一个问题:给定序列\(g_{1\dotsn-1}\),求序列\(f_{0\dotsn-1}\)。其中\(f_i=\sum_{j=1}^if_{i-j}g_j\),边界为\(f_0=1\)。具体可以见【模板】分治FFT-洛谷对于这个问题我们要求做到\(\Theta(n\log^2n)\)的......