区间dp:数字二叉搜索树问题

题面:

给定一些数,讲这些数拼成一颗树,要求这棵树是一颗二叉搜索树,同时任意树边相连的两个节点的gcd(greatest common divisor)都超过1。

(GCD:最大公约数,两个或多个整数共有约数中最大的一个 ,例如8和6的最大公约数是2)

输入第一行一个t,表示数据组数。
对于每组数据,第一行输入一个n,表示数的个数
接下来一行有n个数 a i a_i ai,输入保证是升序的

每组数据输出一行,如果能够造出来满足题目描述的树,输出Yes,否则输出No。

sample input:
2
2
7 17
9
4 8 10 12 15 18 33 44 81

sample output:
No
Yes

sample input:
1
6
3 6 9 18 36 108

sample output:
Yes
在这里插入图片描述

数据组成

在这里插入图片描述

思路:
  • 符合最优子结构所以可以使用dp来做
  • 但是它和普通的dp也有区别,他的dp[i][j]不能通过dp[i-1][j],dp[j][k]这种来得到,需要对于每一个子树的左右子树进行判断,判断是否他的左右子树满足条件
  • 所以使用bool型数组l和r来进行左右子树的条件判断,为true表示从i点到j点作为一颗二叉搜索树的左/右子树可行
  • 进行子树的遍历,枚举区间长度,枚举区间起点终点与区间中的根节点,最后判断整棵树是否符合题目描述
#include<iostream>
#include<cstdio>
#include<string.h>
using namespace std;
bool l[1001][1001];
bool r[1001][1001];
bool dp[1001][1001];
bool flag[1001][1001];
int gcd(int a,int b)
{
 return b==0? a:gcd(b,a%b);
}
void init()
{
 memset(l,false,sizeof(l));
 memset(r,false,sizeof(r));
 memset(dp,false,sizeof(dp));
 memset(flag,false,sizeof(flag));
}
int main()
{
 int t;
 scanf("%d",&t);
 while(t--)
 {
  init();
  int n,a[1111];
  scanf("%d",&n); 
  for(int i=1;i<=n;i++)
  {
   scanf("%d",&a[i]);
   l[i][i]=1;
   r[i][i]=1;
  }
  for(int i=1;i<=n;i++)
  {
   for(int j=1;j<=n;j++)
   {
    if(i==j) continue;
    else if(gcd(a[i],a[j])>=2)
     flag[i][j]=1;
   } 
  }
  for(int i=0;i<n;i++)
  {
   for(int j=1;j+i<=n;j++)
   {
    for(int t=j;t<=i+j;t++)
    {
     if(l[j][t]&&r[t][i+j])
     {
      dp[j][i+j]=true;
      if(flag[j-1][t]) r[j-1][i+j]=1;
      if(flag[t][i+j+1]) l[j][i+j+1]=1;
     }
    }
   }
  }
  if(dp[1][n]) cout<<"Yes"<<endl;
  else cout<<"No"<<endl;
 }
 return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值