題目連結: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 }