宇宙狗的危機(區間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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章