ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

[HEOI2016/TJOI2016]序列(CDQ分治优化DP)

2021-12-10 13:32:01  阅读:192  来源: 互联网

标签:CDQ int dp HEOI2016 区间 左半 转移 DP TJOI2016


洛谷题目传送门

解题思路

题目和最长上升子序列比较像,我们考虑用\(dp\)来解决

设\(f_i\)表示以\(i\)这个位置结束的最长序列的长度,\(Max_i\)表示\(i\)这个位置最大的数,\(Min_i\)表示\(i\)这个位置最小的数,转移时枚举上一个选的元素\(j\)是哪个位置

因为最多只会变一个数,所以对于一个转移点\(j\):

·如果是\(j\)的位置变化,那么必须满足它变的最大的数也是小于\(a_i\)的,也就是\(Max_j\leq a_i\)

·如果是\(i\)的位置变化,那么必须满足它变的最小的数也比\(a_j\)大,也就是\(a_j\leq Min_i\)
也就是我们的转移式是这样的

\[f_i=max(f_j|j<i,Max_j \leq a_i,a_j\leq Min_i)+1 \]

这个形式很像三维偏序的形式,可以使用树套树解决,不过我们知道CDQ可以解决一些树套树能够解决的问题,考虑用CDQ分治优化转移
我们回顾\(CDQ\)分治的过程,左半区间的\(dp\)值可以递归解决,然后我们就知道了左半部分每个位置的\(dp\)值,并且利用归并可以将左半区间的元素按照\(Max\)值升序排序,然后考虑处理左区间对右区间的转移

我们也可以提前对这些元素排序,满足右半部分的元素按照\(a_i\)升序排序,然后我们按照\(CDQ\)分治的套路,维护双指针\(i,j\)表示对于右半区间的元素\(j\),左半区间\(Max\)小于等于\(a_j\)的最远的一个元素的位置是\(i\),这样我们就解决掉了前两维的限制,之后再用树状数组维护第三维,就能完成转移了

然后再递归计算右半区间内部的转移

void CDQ(int l,int r)
{
	if(l==r) //边界条件
	{
		f[q[l]]=max(f[q[l]],1);
		return;
	}
	int mid=(l+r)>>1,top=l;
	for(int i=l;i<=r;i++) if(q[i]<=mid) tmp[top++]=q[i]; //先按照第一维分成左右两部分
	for(int i=l;i<=r;i++) if(q[i]>mid) tmp[top++]=q[i];
	for(int i=l;i<=r;i++) q[i]=tmp[i];
	CDQ(l,mid); //递归计算左边
	top=l;
	int i=l,j=mid+1;
	while(i<=mid&&j<=r)
	{
		if(Max[q[i]]<=a[q[j]])	add(a[q[i]],f[q[i]]),i++; //在保证第二维的情况下用树状数组维护第三维
		else f[q[j]]=max(f[q[j]],ask(Min[q[j]])+1),j++;			
	}
	while(j<=r) f[q[j]]=max(f[q[j]],ask(Min[q[j]])+1),j++;
	for(int k=l;k<=mid;k++) cover(a[q[k]],0);//清空树状数组
	CDQ(mid+1,r); //递归计算右半部分
	i=l;j=mid+1;top=l;
	while(i<=mid&&j<=r) //按照第二维归并,方便之后的计算
	{
		if(Max[q[i]]<=Max[q[j]]) tmp[top++]=q[i++];
		else tmp[top++]=q[j++];
	}
	while(i<=mid) tmp[top++]=q[i++];
	while(j<=r) tmp[top++]=q[j++];
	for(int k=l;k<=r;k++) q[k]=tmp[k];
}

关于这个转移顺序,不能写成下面这种

void CDQ(int l,int r)
{
	CDQ(l,mid);
	CDQ(mid+1,r);
	calc(l,r)
}

原因是我们进行\(dp\)的时候需要保证转移点的值是已经被完全计算好的,不会变的
但是如果按照上面的形式,我们左半区间的\(dp\)值,可能在更高一层才会被转移,所以当前这一层时\(dp\)值并没有被完全计算好,因此转移时错误的

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+7;
typedef long long LL;
int tree[N];
int f[N];
int a[N];
int n;
int lowbit(int x)
{
	return x&(-x);
}
void add(int x,int v)
{
	for(int i=x;i<=1e5;i+=lowbit(i))
	tree[i]=max(tree[i],v);
}
int ask(int x)
{
	int res=0;
	for(int i=x;i;i-=lowbit(i))
	res=max(res,tree[i]);
	return res;
}
void cover(int x,int v)
{
	for(int i=x;i<=1e5;i+=lowbit(i))
	tree[i]=v;
}
int Min[N],Max[N];
int q[N],tmp[N];
void CDQ(int l,int r)
{

	if(l==r)
	{
		f[q[l]]=max(f[q[l]],1);
		return;
	}
	int mid=(l+r)>>1,top=l;
	for(int i=l;i<=r;i++) if(q[i]<=mid) tmp[top++]=q[i];
	for(int i=l;i<=r;i++) if(q[i]>mid) tmp[top++]=q[i];
	for(int i=l;i<=r;i++) q[i]=tmp[i];
	CDQ(l,mid);
	top=l;
	int i=l,j=mid+1;
	while(i<=mid&&j<=r)
	{
		if(Max[q[i]]<=a[q[j]])	add(a[q[i]],f[q[i]]),i++;
		else f[q[j]]=max(f[q[j]],ask(Min[q[j]])+1),j++;			
	}
	while(j<=r) f[q[j]]=max(f[q[j]],ask(Min[q[j]])+1),j++;
	for(int k=l;k<=mid;k++) cover(a[q[k]],0);
	CDQ(mid+1,r);
	i=l;j=mid+1;top=l;
	while(i<=mid&&j<=r)
	{
		if(Max[q[i]]<=Max[q[j]]) tmp[top++]=q[i++];
		else tmp[top++]=q[j++];
	}
	while(i<=mid) tmp[top++]=q[i++];
	while(j<=r) tmp[top++]=q[j++];
	for(int k=l;k<=r;k++) q[k]=tmp[k];
}
bool cmp(int x,int y)
{
	return a[x]<a[y];
}
int m;
int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	{
		int x;
		scanf("%d",&x);
		Min[i]=Max[i]=x;
		a[i]=x;
		q[i]=i;
	}
	for(int i=1;i<=m;i++)
	{
		int x,y;
		scanf("%d %d",&x,&y);
		Min[x]=min(Min[x],y);
		Max[x]=max(Max[x],y);
	}
	sort(q+1,q+n+1,cmp);
	CDQ(1,n);
	int ans=1;
	for(int i=1;i<=n;i++)
	ans=max(ans,f[i]);	
	cout<<ans;
	return 0;
}

标签:CDQ,int,dp,HEOI2016,区间,左半,转移,DP,TJOI2016
来源: https://www.cnblogs.com/jesoyizexry/p/15671273.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有