最后冲刺!!!|

铃狐sama

园龄:1年11个月粉丝:5关注:5

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 中国大陆许可协议进行许可。

posted @   铃狐sama  阅读(286)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起