CDQ分治的优化dp理解
CDQ分治进阶:优化dp
蒟蒻做起来非常的蒙蔽
为什么蒙蔽呢?
因为我没有深刻了解CDQ分治
对于CDQ的深层了解
对于基础的CDQ,我的顺序是可以改变的。
什么顺序:众所周知,CDQ分治分为分治和计算两个部分,这个顺序就是指先分治左右两侧还是先计算中间有mid隔阂的
但是这种顺序要在一个前提下:分治的时候要求是整体按照a(某个值)排序的,计算的时候是要按照b(某个值)l到mid和mid+1到r+1分别排序的
所以呢,如果一开始我就把这个数组按a排序了,那么我先分治再计算就不会有任何影响(类似dfs,在一个所有小区间区间分治完前,这个区间按a的排序一直不会变)
但是,如果我先计算再分治,计算完我的序列此时就相当于按照b分别排序了,已经是乱序了
因此,如果计算再分治之前的话计算完必须要从新按照a排序整个区间
还有呢,有时候可能限制mid左右不同,此时左右应该是不同的排序方式(如例题)
看代码,这几种写法都是对的:
这种写法是先计算再分治
void cdq(int l,int r){
if(l==r){
return ;
}
int mid=(l+r)/2;
// cout<<"test"<<mid<<endl ;
sort(a+l,a+mid+1,cmpb);
sort(a+mid+1,a+r+1,cmpb);
int i,j=l;
for(i=mid+1;i<=r;i++){
while(a[i].b>=a[j].b&&j<=mid){
add(a[j].c,a[j].cnt);
j++;
}
a[i].ans+=query(a[i].c);
// cout<<"test"<<a[i].ans<<endl;
}
for(i=l;i<=j-1;i++){
add(a[i].c,-a[i].cnt);
}
sort(a+l,a+r+1,cmpa);
cdq(mid+1,r);
cdq(l,mid);
}
//-------------------------------------------------------------
这种写法是计算在中间的情况
void cdq(int l,int r){
if(l==r){
return ;
}
int mid=(l+r)/2;
// cout<<"test"<<mid<<endl ;
cdq(l,mid);
sort(a+l,a+mid+1,cmpb);
sort(a+mid+1,a+r+1,cmpb);
int i,j=l;
for(i=mid+1;i<=r;i++){
while(a[i].b>=a[j].b&&j<=mid){
add(a[j].c,a[j].cnt);
j++;
}
a[i].ans+=query(a[i].c);
// cout<<"test"<<a[i].ans<<endl;
}
for(i=l;i<=j-1;i++){
add(a[i].c,-a[i].cnt);
}
sort(a+l,a+r+1,cmpa);
cdq(mid+1,r);
}
最后一个我就不放了,可以去上一篇看
CDQ和DP
众所周知dp是个很√8烦的东西
首先呢,要用CDQ,那么基本上这个dp就有很多限制了,至于限制是什么,可以看例题感受下,大多是大于小于限制
CDQ对于DP而言就是会限定计算和分治的顺序,他要求计算必须在两个分治中间
如果分治都放在前面的话,中间的还没算出来,会导致漏解(中间的无法给后面的提供答案)
如果分治都放在后面的话,前面的还没算出来,也会漏解(前面的无法给中间即后面的提供答案)
所以只能放在中间,这样,前面算出来,中间也就可以完整算出,最后也可以完整算出
注意事项:(警告后人)
1.cdq目的是优化dp,因此树状数组那一部分往往是跟dp柿子相关(如max啊等等),然后树状数组内添加的元素也是dp值,
然后最后答案该是dpn就是dpn,该是max(dp1,dp2..dpn)就是。
2.这一个是个小错误,就是清空时候,看好加了什么就清空什么
先看基础题https://www.luogu.com.cn/problem/P4093
思路已经写在代码里面了
#include<bits/stdc++.h>
using namespace std;
#define int long long
/*
很显然这道题暴力枚举都会G(dp,O(n),询问O(m))
然后是子序列,那么dp[i]=max_dp[j]+1,要求j<i且a[j]<a[i]
现在加上变化: 假设ai/j到达的最小值和最大值分别为li,ri,lj,rj
由于要求的是:所有的条件的全部满足
那么要满足:aj<li 以及 rj<ai;
为什么是这个关系,拿第一个来说,要是aj>=li,那么当ai取li时,不会贡献了.
然后发现其实ai>aj包含在这两个条件内,可以不管了
那么三维偏序看的就很明显了
然后题目要求是“不下降”,限制一下等号就行了
*/
int n,m;
struct node{
int lmin;
int rmax;
int pos;
int val;
}num[100005];
int f[100005];
bool cmppos(node x,node y){
return x.pos<y.pos;
}
bool cmpval(node x,node y){
return x.val<y.val;
}
bool cmpl(node x,node y){
return x.lmin<y.lmin;
}
//------------------------------------
int tree[100005];
int lowbit(int x){
return x&(-x);
}
void add(int pos,int val){
for(int i=pos;i<=100005;i+=lowbit(i)){
tree[i]=max(tree[i],val);
}
}
int query(int pos){
int ret=0;
for(int i=pos;i>=1;i-=lowbit(i)){
ret=max(ret,tree[i]);
}
return ret;
}
void clear(int pos){
for(int i=pos;i<=100005;i+=lowbit(i)){
tree[i]=0;
}
}
//------------------------------------
void cdq(int l,int r){
if(l==r){
return ;
}
int mid=(l+r)>>1;
cdq(l,mid);
sort(num+l,num+mid+1,cmpval);
sort(num+mid+1,num+r+1,cmpl);
int i,j=l;
for(i=mid+1;i<=r;i++){
while(j<=mid&&num[i].lmin>=num[j].val){
add(num[j].rmax,f[num[j].pos]);//找出所有合法j的转移权值(rmax用于限制,f则是权值) 要求num i>rmax j
j++;
}
f[num[i].pos]=max(f[num[i].pos],query(num[i].val)+1);//找到合法对后,取所有能取到的最大值
}
for(i=l;i<=mid;i++){
clear(num[i].rmax);
}
sort(num+l,num+r+1,cmppos);//回复原来的序列
cdq(mid+1,r);
}
signed main(){
ios::sync_with_stdio(false);
cin >> n >> m;
for(int i=1;i<=n;i++){
cin >> num[i].val;
num[i].rmax=num[i].val;
num[i].lmin=num[i].val;
num[i].pos=i;
}
for(int i=1;i<=m;i++){
int x,y;
cin >> x >> y;
num[x].lmin=min(num[x].lmin,y);
num[x].rmax=max(num[x].rmax,y);
}
//此时已经按照位置排好序
for(int i=1;i<=n;i++){
f[i]=1;//初始值
}
cdq(1,n);
// cout<<f[n];
int ans=0;
for(int i=1;i<=n;i++){
ans=max(ans,f[i]);
}
cout<<ans;
}
本文作者:linghusama
本文链接:https://www.cnblogs.com/linghusama/p/17561990.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步