题目链接:https://cn.vjudge.net/problem/UVA-1362
题意:给定一个全为大写字母的序列,(一个大写字母代表树的一个结点),求出有多少颗不同的树满足前序遍历的序列恰好为所给的序列?
在白书上讲解了一种方法,用递归来实现的,设S为所输入的序列,然后dp[i][j](代表序列i到j的种数),为子序列Si,S(i+1),……Sj,对应的树的个数,特别的当i==j时,也就是说当前子序列只有一个字符串,那么建树的方法肯定就只有一种,即dp[i][j]=1;,当Si不等于Sj时dp[i[j]=0(因为如果要能构成一个分支或者是一棵树那么起点和终点应该是相同的);
然后开始遍历分支,设第一个分支在Sk时回到树根(即Si==Sk),则这个分支对应的序列S(i+1),……,S(k-1)就能构成分支,那么方案数就是dp[i+1][k-1];其他分支对应的访问序列为Sk,……,Sj,那么方案数应该是dp[k][j];因此会有一个递推关系式
dp[i[j]=sum{dp[i+1][k-1]*dp[k][j]|i+2<=k<=j&&Si==Sk==Sj};
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
using namespace std;
typedef long long ll;
#define rep(i,a,b) for(int i=a;i<=b;i++)
const int N=1e6+7;
const int MOD= 1e9;
char str[400];
int d[400][400];
int dfs(int i,int j){
if(i==j) return d[i][j]=1;
if(str[i]!=str[j]) return d[i][j]=0;
// int &ans=d[i][j];
// if(ans>=0) return ans;
if(d[i][j]!=-1) return d[i][j];
ll ans=0;
rep(k,i+2,j)
if(str[i]==str[k])
ans=(ans+(ll)dfs(i+1,k-1)*(ll)dfs(k,j))%MOD;
return d[i][j]=ans;
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
#endif // ONLINE_JUDGE
while(scanf("%s",str)!=EOF)
{
memset(d,-1,sizeof d);
printf("%d\n",dfs(0,strlen(str)-1));
}
return 0;
}
其实我感觉类似区间DP的思想,因为每次递归解决就是不断的缩小区间,然后再相加,看了一位大佬的blog发现是可以用区间DP来实现的,只不过定义状态转移方程的数据类型是不相同的
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
using namespace std;
typedef long long ll;
#define rep(i,a,b) for(int i=a;i<=b;i++)
const int N=1e6+7;
const int MOD= 1e9;
char str[400];
ll dp[400][400];
int main()
{
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
#endif // ONLINE_JUDGE
while(scanf("%s",str)!=EOF)
{
memset(dp,0,sizeof dp);
int n=strlen(str);
rep(i,0,n-1) dp[i][i]=1;
for(int len=1;len<n;len++){
for(int j=0;j+len<n;j++){
for(int k=1;k<=len;k++){
if(str[j]==str[j+k])
dp[j][j+len]=(dp[j][j+len]+dp[j+1][j+k-1]*dp[j+k][j+len])%MOD;
}
}
}
printf("%lld\n",dp[0][n-1]);
}
return 0;
}