前言
作为一名DP矮子,就从这道看似简单的区间DP开始学习区间DP吧
以下文字纯属个人理解
让人觉得很淦的提示: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;
}