[csp模拟4] T4 —— 宇宙狗的危机(区间DP)

题意

在这里插入图片描述
在这里插入图片描述

Input

在这里插入图片描述

Output

在这里插入图片描述
无行末空格

输入样例

在这里插入图片描述


在这里插入图片描述

输出样例

Yes


No
Yes

提示

在这里插入图片描述
在这里插入图片描述


分析


  • 题目分析

以为这道题是二叉搜索树或者是升级版找规律,结果受大佬启发才知道是区间DP🙌🏼DP真的是太难了,自己想脑子不大好使【orz】

简单解析一下求解方法,

  1. 利用三个数组来标记三个不同状态:
  • 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。

  1. 用数组GCD[i][j]记录任意两个数字在二叉搜索树中是否可以直接相连(也就是gcd是否大于1)

  2. 状态转移

  • 若一个区间内存在一个点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的右孩子或左孩子是因为题目给出的数据满足升序,即序号越小的数字越小。而在二叉树中,比根小的节点作为根的左孩子,比根大的节点作为根的右孩子。

  1. 若完整区间[0,n - 1]是一个合法区间,即可以构成一个合法二叉树,则说明有解;否则无解。

总结

  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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

天翊藉君

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值