算法随笔——cdq分治

学习链接
cdq分治是一种离线算法,一般解决以下三种问题:

  1. cdq 分治解决和点对有关的问题

  2. cdq 分治优化 1D/1D 动态规划的转移

  3. 通过 cdq 分治,将一些动态问题转化为静态问题

大致思想是对于区间 [l,r],先解决 [l,mid][mid+1,r],在计算区间 [l,mid] 对区间 [mid+1,r] 的贡献。

偏序

第一种即偏序问题。

  • 二维偏序
    例如求逆序对,可以用归并排序或树状数组解决。
  • 三维偏序
    P3810 【模板】三维偏序(陌上花开)
    我们对于第一维排序,然后对于区间 [l,mid][mid+1,r],以第二维为关键字再次排序,左区间和右区间内部已经算好,只需计算左区间对右区间的贡献。这时,运用归并排序类似的双指针算法加上树状数组计算第三维,整个算法完成了。
点击查看代码
// Problem: P3810 【模板】三维偏序(陌上花开)
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P3810
// Memory Limit: 500 MB
// Time Limit: 1000 ms
// Author: Eason
// Date:2024-03-21 20:32:17
// 
// Powered by CP Editor (https://cpeditor.org)

#include<bits/stdc++.h>
using namespace std;
#define ull unsigned long long
#define ll long long
#define INF 0x3f3f3f3f
#define re register
#define il inline
#define gc getchar
inline int read()
{
	int f=1,k=0;
	char c = getchar();
	while (c <'0' || c > '9')
	{
		if (c=='-') f=-1;
		c=getchar();
	}
	while(c >= '0' && c <= '9')  k = (k << 1)+(k << 3)+(c^48),c=getchar();
	return k*f;
}

const int N = 2e5+5;

int n,k,c[N],ans[N];
struct node
{
	int x,y,z,cnt,res;
}d[N],a[N];

int lowbit(int x){return x & -x;}
int query(int x)
{
	int res = 0;
	for (int i = x;i;i-=lowbit(i)) res += c[i];
	return res;
}
void add(int x,int v)
{
	for (int i = x;i <= k;i += lowbit(i)) c[i] += v;
}

bool cmp1(node a,node b)
{
	if (a.x != b.x) return a.x < b.x;
	if (a.y != b.y) return a.y < b.y;
	return a.z < b.z;
}
bool cmp2(node a,node b)
{
	if (a.y != b.y) return a.y < b.y;
	return a.z < b.z;
}

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,cmp2);
	sort(a + mid + 1,a + r + 1,cmp2);
	int j = l;
	for (int i = mid + 1;i <= r;i++)
	{
		while (a[i].y >= a[j].y && j <= mid) add(a[j].z,a[j].cnt),j++;
		a[i].res += query(a[i].z);
	}
	for (int i = l;i < j;i++) add(a[i].z,-a[i].cnt);
	return;
}

int main()
{
	cin >> n >> k;
	for (int i = 1;i <= n;i++) d[i] = {read(),read(),read()};
	sort(d + 1,d + n + 1,cmp1);
	int ct = 0;
	for (int i = 1;i <= n;i++)
		if (d[i].x == d[i-1].x && d[i].y == d[i-1].y && d[i].z == d[i-1].z) a[ct].x = d[i].x,a[ct].y = d[i].y,a[ct].z = d[i].z,a[ct].cnt++;
		else a[++ct] = d[i],a[ct].cnt = 1;
	cdq(1,ct);
	
	for (int i = 1;i <= ct;i++) ans[a[i].res + a[i].cnt - 1] += a[i].cnt;
	for (int i = 0;i < n;i++) cout << ans[i] << endl;
	return 0;
}

优化动态规划转移

例题:给定两个序列 ai,bi,求满足 i<jai<ajbi<bj 的最长子序列。
容易得到 dp 式为:dp[i]=dp[j]+1 (j 需要满足上面的条件)

暴力转移是 O(n2) 的,但可以观察到上面的条件和三维偏序十分类似,于是需要用到cdq分治。
但是在动态规划中cdq分治顺序变为:先[l,mid],计算左对右的贡献,再[mid+1,r],原因是左区间对右区间的贡献可能影响到右区间内部的计算。

因为分治顺序的不同,在对于左右区间排序时需要新开一个数组,不可以在原来的序列上进行修改,因为还要在最后还有计算右区间。

代码是类似的。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define INF 0x3f3f3f3f
#define re register
#define PII pair<int,int>
int read()
{
	int f=1,k=0;char c = getchar();
	while(c <'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
	while(c>='0'&&c<='9')k=(k<<1)+(k<<3)+(c^48),c=getchar();
	return k*f;
}

const int N = 1e5+5;
int n;
int f[N],dp[N];
int c[N];
struct node
{
	int id,x,y,f;
}a[N];

int lowbit(int x){return x & -x;}
int query(int x)
{
	int res = 0;
	for (int i = x;i;i -= lowbit(i)) res = max(res,c[i]);
	return res;
}
void add(int x,int v)
{
	for (int i = x;i <= n;i+=lowbit(i)) c[i] = max(c[i],v);
}
void clear(int x)
{
	for (int i = x;i <= n;i+=lowbit(i)) c[i] = 0;
}

bool cmp(node a,node b)
{
	return a.x <= b.x;
}
node b[N];
void cdq(int l,int r)
{
	if (l == r) 
	{
		return;
	}
	int mid = l + r >> 1;
	cdq(l,mid);
	for(int i=l; i<=r; i++) b[i]=a[i];
	sort(b + l,b + mid + 1,cmp);
	sort(b + mid + 1,b + r + 1,cmp);
	int j = l; //双指针
	for (int i = mid + 1;i <= r;i++)
	{ 
		while (b[j].x <= b[i].x && j <= mid) add(b[j].y,dp[b[j].id]),j++;
		dp[b[i].id] = max(dp[b[i].id],query(b[i].y) + 1); //计算贡献
	}
	
	for (int i = l;i <= mid;i++) clear(b[i].y);
	cdq(mid + 1,r);
}
int main()
{	
	cin >> n;
	for (int i = 1;i <= n;i++) a[i].x = read();
	for (int i = 1;i <= n;i++) a[i].y = read(),a[i].id = i,dp[i] = 1;
	cdq(1,n);
	int ans = 0;
	for (int i = 1;i <= n;i++) ans = max(ans,dp[i]);
	cout << ans << endl;
	return 0;
	
}
posted @   codwarm  阅读(6)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
点击右上角即可分享
微信分享提示