Codeforces 509F Progress Monitoring:区间dp【根据遍历顺序求树的方案数】

题目链接: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 }
原文地址:https://www.cnblogs.com/Leohh/p/8206941.html