题意
Input
Output
无行末空格
输入样例
输出样例
Yes
No
Yes
提示
分析
- 题目分析
以为这道题是二叉搜索树或者是升级版找规律,结果受大佬启发才知道是区间DP🙌🏼DP真的是太难了,自己想脑子不大好使【orz】
简单解析一下求解方法,
- 利用三个数组来标记三个不同状态:
- root_right[i][j] = true —— 以序号i的数字为根,到右端点j可以构成i 的右子树
- root_left[i][j] = true —— 以序号j的数字为根,到左端点i可以构成j的左子树
- interval[i][j] = true —— i 到 j 是可以构成合法二叉搜索树的区间
显然,任意一个端点为根时,到它自己都可以构成右子树和左子树,也就是root_right[i][i] / root_left[i][i]都是以自己为根,没有叶子的树。因此这些情况都初始化为true。
-
用数组GCD[i][j]记录任意两个数字在二叉搜索树中是否可以直接相连(也就是gcd是否大于1)
-
状态转移
- 若一个区间内存在一个点k,使得root_left[i][k]和root_right[k][j]都成立,则代表着该区间[i,j]是一个可以构成合法二叉树的区间,则interval[i][j] = true。
if( root_left[l][k] && root_right[k][r] )
interval[l][r] = 1;
-
若一个合法区间[i,j]内的根k与左端点的前一个端点i - 1能够直接相连,就代表着k可以作为端点i - 1的右孩子,也就是说这个区间以k为根形成的二叉搜索树可以构成端点i - 1的右子树。因此root_right[i - 1][j]为true。
-
同理,若一个合法区间[i,j]内的根k与右端点的后一个端点j + 1能够直接相连,就代表着k可以作为端点就j + 1的左孩子,也就是说这个区间以k为根形成的二叉搜索树可以构成端点j + 1的左子树。因此root_left[i][j + 1]为true。
if( GCD[k][l - 1] )
root_right[l - 1][r] = 1;
if( GCD[k][r + 1] )
root_left[l][r + 1] = 1;
注意,可以直接判定k是i - 1或j + 1的右孩子或左孩子是因为题目给出的数据满足升序,即序号越小的数字越小。而在二叉树中,比根小的节点作为根的左孩子,比根大的节点作为根的右孩子。
- 若完整区间[0,n - 1]是一个合法区间,即可以构成一个合法二叉树,则说明有解;否则无解。
总结
- dp还是太难了😭
代码
//
// main.cpp
// lab3
//
//
#include <vector>
#include <string.h>
#include <iostream>
using namespace std;
int a[705];
bool GCD[705][705];
bool interval[705][705];
bool root_left[705][705];
bool root_right[705][705];
int gcd(int a,int b)
{
return b == 0 ? a : gcd(b, a % b);
}
void initialize()
{
memset(interval, false, sizeof(interval));
memset(root_right, false, sizeof(root_right));
memset(root_left, false, sizeof(root_left));
}
int main()
{
ios::sync_with_stdio(false);
int t = 0,n = 0;
cin>>t;
while ( t-- )
{
cin>>n;
initialize(); //初始化
for( int i = 0 ; i < n ; i++ ) //输入
cin>>a[i];
for( int i = 0 ; i < n ; i++ ) //记录任意两个数之间是否能相连
{
for( int j = 0 ; j < n ; j++ )
{
if( gcd(a[i], a[j]) > 1 )
GCD[i][j] = true;
else
GCD[i][j] = false;
}
root_left[i][i] = true; //以自己为根的左右子树都是合法的
root_right[i][i] = true;
}
for( int length = 1 ; length <= n ; length++ ) //循环所有区间长度
{
for(int l = 0 , r = l + length - 1 ; r < n ; l++, r++) //循环该长度下的所有左右端点
{
for( int k = l ; k <= r ; k++ ) //循环该区间中的所有点
{
//若以当前点为根,到左右端点构成的左右子树都成立
if( root_left[l][k] && root_right[k][r] )
{
interval[l][r] = 1; //则说明该区间内所有数符合要求
//若该区间的二叉树根和左端点前一个能够连通
//说明这个根可以作为右孩子与左端点前一个相连
//则说明以左端点为根,到当前区间的右端点能够构成右子树
if( GCD[k][l - 1] )
root_right[l - 1][r] = 1;
if( GCD[k][r + 1] ) //同理
root_left[l][r + 1] = 1;
}
}
}
}
if( interval[0][n - 1] ) //直接判断整个区间是否都满足要求
cout<<"Yes"<<endl;
else
cout<<"No"<<endl;
}
return 0;
}