算法随笔——cdq分治
学习链接
cdq分治是一种离线算法,一般解决以下三种问题:
-
cdq 分治解决和点对有关的问题
-
cdq 分治优化 1D/1D 动态规划的转移
-
通过 cdq 分治,将一些动态问题转化为静态问题
大致思想是对于区间
偏序
第一种即偏序问题。
- 二维偏序
例如求逆序对,可以用归并排序或树状数组解决。 - 三维偏序
P3810 【模板】三维偏序(陌上花开)
我们对于第一维排序,然后对于区间 ,以第二维为关键字再次排序,左区间和右区间内部已经算好,只需计算左区间对右区间的贡献。这时,运用归并排序类似的双指针算法加上树状数组计算第三维,整个算法完成了。
点击查看代码
// 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;
}
优化动态规划转移
例题:给定两个序列
容易得到 dp 式为:
暴力转移是
但是在动态规划中cdq分治顺序变为:先
因为分治顺序的不同,在对于左右区间排序时需要新开一个数组,不可以在原来的序列上进行修改,因为还要在最后还有计算右区间。
代码是类似的。
点击查看代码
#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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通