宇宙狗的危机(区间dp)

问题描述

在瑞神大战宇宙射线中我们了解到了宇宙狗的厉害之处,虽然宇宙狗凶神恶煞,但是宇宙狗有一个很可爱的女朋友。

最近,他的女朋友得到了一些数,同时,她还很喜欢树,所以她打算把得到的数拼成一颗树。

这一天,她快拼完了,同时她和好友相约假期出去玩。贪吃的宇宙狗不小心把树的树枝都吃掉了。所以恐惧包围了宇宙狗,他现在要恢复整棵树,但是它只知道这棵树是一颗二叉搜索树,同时任意树边相连的两个节点的gcd(greatest common divisor)都超过1。

但是宇宙狗只会发射宇宙射线,他来请求你的帮助,问你能否帮他解决这个问题。

补充知识:

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

一个简短的用辗转相除法求gcd的例子:

int gcd(int a,int b){return b == 0 ? a : gcd(b,a%b);}

Input

输入第一行一个t,表示数据组数。

对于每组数据,第一行输入一个n,表示数的个数

接下来一行有n个数​,输入保证是升序的。

Output

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

无行末空格。

Sample input & output

Sample input1
1
6
3 6 9 18 36 108
Sample output1
Yes
Sample input2
2
2
7 17
9
4 8 10 12 15 18 33 44 81
Sample output2
No
Yes

数据范围

在这里插入图片描述

提示

第一个样例可以构建下图所示的二叉搜索树
在这里插入图片描述

解题思路

这道题开始没想到用区间dp来做,想了一个自己认为“绝妙的方法”。利用栈来维护一条主链,方法就不说了,毕竟结果炸了。这个方法挺“绝命的”,样例全过,直接0分。

二叉搜索树具有明显的子结构特性,这个题虽然要构成一个二叉树,但是给出的是一个区间,因此这个题还是要用区间dp来做的,树的结构不能丢,我们定义两个数组:l[i][j],r[i][j]l[i][j],r[i][j]分别表示以ii为根,向左到jj可以作为ii的左子树,向右到jj可以作为ii的右子树。树结构非常重要,虽然是区间dp,但是这个结果要构成一个二叉搜索树,因此必须有这两个数组。

然后用dp[i][j]dp[i][j]表示iji\sim j能构成一棵树。函数最后返回dp[1][n]dp[1][n]。(我以前尝试过直接dp[i][j]dp[i][j]表示iji\sim j能构成一棵树,然后拼接用dp[i][k],dp[k][j]dp[i][k],dp[k][j]拼接,这样错的原因是,dp[i][k]dp[i][k]能够构成一棵树,不一定表示左子树,dp[k][j]dp[k][j]能够构成一棵树,不一定表示右子树,那么就不一定能拼接起来。2,3,5,30这样四个节点数据就可以卡死。)

l[k][i]==r[k][j]==truel[k][i]==r[k][j]==true时,dp[i][j]=truedp[i][j]=true,此时iji\sim j是以kk为根的二叉搜索树。

状态转移是每次转移一个点,在dp[i][j]=truedp[i][j]=true的情况下:
l[j+1][i]=gcd(a[j+1],a[k]),(ikj)r[i1][j]=gcd(a[i1],a[k]),(ikj) l[j+1][i]\hspace{3pt}|=gcd(a[j+1],a[k]),(i\le k\le j)\\ \quad\\ r[i-1][j]\hspace{3pt}|=gcd(a[i-1],a[k]),(i\le k\le j)
转移方程为什么这么写呢,先看第一个方程,首先这个时候我们已经保证了从iijj这段区间可以构成一棵二叉搜索树,并且根节点是kk,注意这个根节点很重要。如果说gcd(a[j+1],a[k])>1gcd(a[j+1],a[k])>1了,那么二者之间就可以连边,并且注意ll数组的定义:表示以ii为根,向左到jj可以作为ii的左子树,那么a[j+1]a[j+1]的左儿子就是a[k]a[k]了。在枚举kk的过程中,只要有一次可以连边,那么l[j+1][i]=truel[j+1][i]=true。所以中间使用的是=|=符号。第二个方程同理。

完整代码

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <string>
#include <algorithm>
using namespace std;
const int maxn=700+100;
int t,n,a[maxn];
bool dp[maxn][maxn],g[maxn][maxn],l[maxn][maxn],r[maxn][maxn];
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));
    for (int i=1; i<=n; i++) {
        l[i][i]=r[i][i]=dp[i][i]=true;
        for (int j=1; j<=n; j++)
            g[i][j]=gcd(a[i],a[j])>1;
    }
}
bool IsBinarySearchTree(){
    for (int len=1; len<=n; len++){
        for (int i=1; i<=n-len+1; i++){
            int j=i+len-1;
            for (int k=i; k<=j; k++){
                if(l[k][i] && r[k][j]){
                    dp[i][j]=true;
                    l[j+1][i]|=g[j+1][k];
                    r[i-1][j]|=g[i-1][k];
                }
            }
        }
    }
    return dp[1][n];
}
int main(){
    scanf("%d",&t);
    while(t--){
        scanf("%d",&n);
        for (int i=1; i<=n; i++) scanf("%d",&a[i]);
        init();
        if(IsBinarySearchTree()) printf("Yes\n");
        else printf("No\n");
    }
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章