[CF1025D] Recovering BST(区间DP入门)

前言

作为一名DP矮子,就从这道看似简单的区间DP开始学习区间DP吧

以下文字纯属个人理解

让人觉得很淦的提示:CF是少爷机

题目

洛谷

CF

讲解

何为区间DP?

DP的预处理即为求出小区间的状态

我们通过小区间更新次小区间,最后到大区间

完成最后对答案的计算

此为区间DP

区间DP的特征?

首先它要长得像个DP (完全凭感觉),比如数据范围比较小之类的

就目前我遇到的区间DP而言两个小区间能够满足合成为大区间的性质(通常时间复杂度要比状态数高一阶)

当然也可以是一个区间向旁边扩展一个格子(通常时间复杂度即为状态数)

大致做法

首先我们要把DP状态定义出来(难道这不是每个DP的基操?)

先更新小区间!!!

正如我们之前提到的,我们一定要先求出小区间的解

不然你怎么更新大区间?

本题思路

由于这是一棵二叉搜索树

所以我们只需将左右儿子合并成一棵树,作为另一个根的某个儿子

长得就是一副区间DP的样子

确定了方法后,我们来尝试定义状态

由于我们有左右两个儿子,我们使用两个DP数组:

[egin{cases} L[l][r]:区间[l,r-1]作为左儿子,连向根r的状态\ R[l][r]:区间[l+1,r]作为右儿子,连向根l的状态\ end{cases}]

我们按部就班,先更新最小区间

在这里,我们发现DP边界即为(l=r)的情况

[egin{cases} L[l][r] = 1 (l = r)\ R[l][r] = 1 (l = r)\ end{cases}]

然后我们可以预处理出两个数能否连边,使用(pd[i][j])数组存储,时间复杂度为(O(n^2logn))

最后是DP转移,即将两个小区间拼起来

考虑枚举区间[l,r]的根(i (l<=i<=r))

如果满足拼起来的条件(if(L[l][i] && R[i][r]))

试图更新更大的区间,考虑向左或右扩展:

[egin{cases} if(pd[i][r+1]) L[l][r+1] = 1\ if(pd[l-1][i]) R[l-1][r] = 1\ end{cases}]

如果拼起来之后的区间为[1,n]了,即有解,输出(Yes)即可

反之,如果到最后也没有跑出有解,输出(No)

实在没懂看看代码也就懂了

代码

//12252024832524
#include <cstdio>
#include <algorithm>
using namespace std; 

typedef long long LL;
const int MAXN = 705;
int n;
int a[MAXN];
bool pd[MAXN][MAXN],L[MAXN][MAXN],R[MAXN][MAXN];

int gcd(int x,int y)
{
	if(!y) return x;
	return gcd(y,x%y);
}

int main()
{
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	n = Read();
	for(int i = 1;i <= n;++ i) a[i] = Read(),L[i][i] = R[i][i] = 1;
	for(int i = 1;i <= n;++ i)
		for(int j = i+1;j <= n;++ j)
			pd[i][j] = pd[j][i] = (bool)(gcd(a[i],a[j])-1);
	for(int l = n;l >= 1;-- l)//细品,从小到大枚举区间
		for(int r = l;r <= n;++ r)
			for(int i = l;i <= r;++ i)//枚举根 
			{
				if(L[l][i] && R[i][r])
				{
					if(l == 1 && r == n) {printf("Yes");return 0;}
					if(pd[i][r+1]) L[l][r+1] = 1;
					if(pd[l-1][i]) R[l-1][r] = 1;
				}
			}
	printf("No");
	return 0;
}
原文地址:https://www.cnblogs.com/PPLPPL/p/13393332.html