《训练指南》——6.11

Poj 2795:

  给出一棵多叉树,每个节点都有大写字母,那么按照类似前序遍历的方法(区别在于回溯访问的点也记录),依次记录访问过的节点所记录的大写字母,直到访问完所有的点并回溯到根节点,我们会得到一个有大写字母组成的字符串序列,那么现在给出一个字符串序列S,问你共有多少个多叉树满足按照上面提到的遍历方法,得到相同的字符串序列。

  分析:首先这道题目很大的一个障碍就是对题意的理解,通过样例我们能够解决理解题意上的很多问题,能够看到,题目所说的遍历方式非常像前序遍历但是它回溯经过访问过的点依然需要进行记录。

  那么现在考虑给出一个字符串S,我们如何将其子问题化从而建立状态转移方程进行阶段性求解。设置d(i,j)用于记录遍历节点满足子序列S[i]S[i+1]…S[j]的多叉树种类,那么对于d(i,j),我们可以设置一个参量k∈[i+2,j],首先我们得到d(i+1,k-1),再得到d(k,j),能够看到这两个过程相互独立,随后遍历参数k,vk-1作为前一棵子树回溯到起始点前的最后一个顶点,在k取不同值的时候,保证了整个树结构不会出现重复的情况,因此我们能够得到如下的递推公式:

  d(i,j) = ∑d(i+1,k-1)d(k,j).

  这是对于最一般的情况,对于S[i] != S[j],有d(i,j) = 0.

  而对于最小的子问题,有d(i,i) = 1.

  这里可能会有人提出疑问,为何k取不到i呢?容易看到k取i和k去j两种情况是重复的。

  简单的参考代码如下:

 

#include<cstdio>

#include<cstring>

using namespace std;

int const maxn = 305;

int const mod = 1000000000;

char S[maxn];

int d[maxn][maxn];

int dp(int i , int j)

{

     if(i == j)       return 1;

     if(S[i] != S[j]) return 0;

     int  & ans = d[i][j];

     if(ans >= 0)      return ans;

 

        ans = 0;

            for(int k = i + 2;k <= j;k++)

                 if(S[i] == S[k])   //这个限制条件不要漏掉

                  ans = (ans + (long long)dp(i+1,k-1)*(long long)dp(k,j))%mod; //这里考虑两个int相乘可能会爆int,再次在计算过程中将其强制转换成long long

 

        return ans;

}

 

int main()

{

    memset(d , -1 , sizeof(d));

     while(scanf("%s",S) != EOF)

     {

           printf("%d\n",dp(0,strlen(S)-1));

     }

     return 0;

}

 

posted on 2016-06-12 09:45  在苏州的城边  阅读(216)  评论(0编辑  收藏  举报

导航