【6.10校內test】T3 加分二叉樹

加分二叉樹【題目連接】ios

感受我超廢數組


這道題當時壓根就不會qwq(我卻是挺適合寫rand的qwq)優化

對於暴力的作法:spa

  1. 輸入數據,定義數組men[i][i]=v[i](輸入的第二行);
  2. dfs:
    1. dfs 1—n,首先是幾個臨界狀態:
      1. 當左或右子樹爲空,即L>R(use L and R replace l and r to be clear)時,返回1
      2. 當L==R時,顯然它的分數爲它自己,因此return d[L] or d[R]
      3. 還有一個小優化,就是當咱們以前已經算過某一段[L,R]時,能夠直接拿來取用,即if(mem[l][r]>0)return mem[l][r];

優化的效果:優化前:優化後:效果仍是很明顯的;code

                                4.而後枚舉每一個點作這棵子樹的根(for循環)這裏爲了一會輸出前序遍歷,因此要開數組root[i][j]記錄每一段的根是什麼;blog

    前序遍歷:前序遍歷:先遍歷根節點,而後左側結點,右側結點(根左右);(插一  句,對於三種順序的遍歷,咱們能夠這樣理解:前序遍歷,根在三個字的最前面(根左右);中序遍歷,根在左右之間(左 根右);後序遍歷,根在左右以後(左右根))所以咱們的輸出以下:
    1. 先判斷L==R?輸出L or R:繼續;由於在dfs時咱們並無記錄L==R時的root值(直接return 了)
    2. 判斷輸出解的範圍,若是L>R顯然接下來的輸出都是無效的,直接return;
    3. 能夠開始輸出根root了,輸出root[L][R]以後,再遞歸的輸出左子樹和右子樹(先左後右)

以上就是暴力DFS的思路,如下是代碼:遞歸

#include<iostream>
#include<cstdio>
using namespace std;
int n,v[31],mem[31][31],root[31][31];
int dfs(int l,int r){
    if(mem[l][r]>0)return mem[l][r];//當這個點已經被計算過期,直接返回 
    if(l==r)return v[l];//當只有一個點時,它的值就爲自己 
    if(r<l)return 1;//當出現左右顛倒的狀況,也就是空子樹,加分爲1 
    for(int i=l;i<=r;i++){//分別枚舉每一個點作子樹的根 
        int p=dfs(l,i-1)*dfs(i+1,r)/*左子樹的值×右子樹的值*/+mem[i][i]/*+根結點的值*/;
        if(p>mem[l][r]){
            mem[l][r]=p;/*求最大值*/root[l][r]=i/*記錄路徑用來前序遍歷*/;
        }
    }
    return mem[l][r];
}
void print(int l,int r){//前序遍歷 
    if(r<l)return;
    if(l==r){printf("%d ",l);return;}
    printf("%d ",root[l][r]);
    print(l,root[l][r]-1);
    print(root[l][r]+1,r);
}
int main(){
    freopen("binary.in","r",stdin);
    freopen("binary.out","w",stdout); 
    scanf("%d",&n);
    for(int i=1;i<=n;i++)scanf("%d",&v[i]),mem[i][i]=v[i];
    printf("%d\n",dfs(1,n));
    print(1,n);
    return 0;
}

對於被稱之爲正解的作法:ci

 區間dp的作法:get

  1. 數組dp[i][j],表示區間[i,j)內的最高加分;
  2. 幾種狀況:
    1. 對於以一個點建子樹,dp[i][i+1](由於是開區間,因此後面要+1)就是對應的輸入的加分
    2. 對於以2~n個點建子樹,須要一套for循環進行區間dp,最後的答案是dp[1][n+1];區間dp以下:

第一層for循環l:枚舉以幾個點建子樹,能夠是2~n;io

第二層for循環i:枚舉一個區間,需知足i+l<=n+1(舉個例子:n=5,以兩個點建子樹時,i的取值分別爲1,2,3,4,相應的區間爲[1,2],[2,3],[3,4],[4,5])

第三層for循環k:枚舉樓上劃分出的區間內以哪一個點爲根,計算加分,記錄其中的最大值(小注意:還要開一個數組用來前序遍歷,遍歷和dfs的思想是同樣的,記錄的話也同樣,這裏用w數組);

樣例:

#include <iostream>
using namespace std;
int n;
int d[31];
long long dp[31][32]; //dp[i][j] -> answer in [i,j)
int w[31][32];
void dfs(int l, int r)
{
    cout << w[l][r] << ' ';
    if (w[l][r] > l)
        dfs(l, w[l][r]);
    if (w[l][r] + 1 < r)
        dfs(w[l][r] + 1, r);
}
int main()
{
    cin >> n;
    for (int i = 1; i <= n; i++)
        cin >> dp[i][i + 1], dp[i][i] = 1, w[i][i + 1] = i;
    for (int l = 2; l <= n; l++)
    {
        for (int i = 1; i + l <= n + 1; i++)
        {
            int j = i + l;
            for (int k = i; k < j; k++)
            {
                if (dp[i][k] * dp[k + 1][j] + dp[k][k + 1] > dp[i][j])
                {
                    dp[i][j] = dp[i][k] * dp[k + 1][j] + dp[k][k + 1];
                    w[i][j] = k;
                }
            }
        }
    }
    cout << dp[1][n + 1] << endl;
    dfs(1, n + 1);
}

 end-

相關文章
相關標籤/搜索