Codeforces 509F Progress Monitoring:區間dp【根據遍歷順序求樹的方案數】

Leohh發表於2018-01-05

題目連結:http://codeforces.com/problemset/problem/509/F

題意:

  告訴你遍歷一棵樹的方法,以及遍歷節點的順序a[i],長度為n。

  問你這棵樹有多少種可能的形態。

  遍歷方法:

    used[1 ... n] = {0, ..., 0};

 

    procedure dfs(v):

      print v;

      used[v] = 1;

      for i = 1, 2, ..., n:

        if (a[v][i] == 1 and used[i] == 0):

          dfs(i);

 

    dfs(1);

 

題解:

  表示狀態:

    dp[i][j] = numbers

    表示a[i to j]這些節點,以a[i]為根所能構成的樹的方案數。

 

  找出答案:

    ans = dp[1][n]

 

  如何轉移:

    dp[i][j]一定以a[i]為根,且第一個訪問的子節點一定是a[i+1]。

    所以將dp[i][j]分為三部分:

      (1)根節點a[i]

      (2)最左邊的一棵子樹a[i+1 to k]

      (3)右邊剩下的一堆節點a[k+1 to j]

    那麼根據乘法原理:

      dp[i][j] = ∑ dp[i+1][k]*右邊一堆節點的方案數

 

    然而遍歷是根據節點編號從小到大訪問兒子節點的。

    這就要求,對於後面的一堆節點,如果其中某一個a[x]要和a[i+1]在同一層,那麼一定要有a[i+1]<a[x]。

    又因為對於右邊的一堆節點來說,最先訪問到的肯定是a[k+1]。

    所以就是要保證:a[i+1]<a[k+1] or a[k+1]不存在

    這樣就不用管後面的節點了,愛咋分咋分……

 

    接下來考慮如何求右邊一堆節點的方案數。

    a[k+1 to j]形成森林的方法數 = a[k to j]以a[k]為根的子樹的方法數。

    所以右邊一堆節點的方案數 = dp[k][j]

 

    最終就是:

      if(a[i+1]<a[k+1] || k==j)

        dp[i][j] += dfs(i+1,k)*dfs(k,j);

      用記憶搜搞就行。

 

  邊界條件:

    dp[i][i] = 1

 

AC Code:

 1 #include <iostream>
 2 #include <stdio.h>
 3 #include <string.h>
 4 #define MAX_N 505
 5 #define MOD 1000000007
 6 
 7 using namespace std;
 8 
 9 int n;
10 int a[MAX_N];
11 long long dp[MAX_N][MAX_N];
12 
13 long long dfs(int i,int j)
14 {
15     if(i==j) return 1;
16     if(dp[i][j]!=-1) return dp[i][j];
17     dp[i][j]=0;
18     for(long long k=i+1;k<=j;k++)
19     {
20         if(a[i+1]<a[k+1] || k==j)
21         {
22             dp[i][j]+=dfs(i+1,k)*dfs(k,j);
23             dp[i][j]%=MOD;
24         }
25     }
26     return dp[i][j];
27 }
28 
29 int main()
30 {
31     cin>>n;
32     for(int i=1;i<=n;i++) cin>>a[i];
33     memset(dp,-1,sizeof(dp));
34     cout<<dfs(1,n)<<endl;
35 }

 

相關文章