HDU-5396 Expression(区间DP+组合数)

题意:给定一串包含n个整数和n-1个运算符(包括+-*)的算式,每次操作会将两个数通过它们之间的运算符运算合并在一起,求所有不同合并最终答案的总和%(1e9+7)(合并顺序不同的两种合并方式称为不同的)。(n<=100)

首先不必说,明显是区间DP,dp[L][R]表示数的区间[L,R]所有不同的合并方式的结果总和,而这个值似乎不好转移。

在数的区间[L,R]中,符号的区间为[L,R-1],为了求得所有的情况总和,我们可以把符号作为合并的中心,枚举这个区间中最后合并的符号k,数的区间便分为了[L,k] [k+1,R],符号的区间分为了[L,k-1] k [k+1,R-1],而对符号区间[L,k-1],不同的合并顺序有(k-L)!种;对于符号区间[k+1,R-1]不同的合并顺序有(R-k-1)!种。所以,还需要预处理出阶乘表。

对于三种符号,还需要分开讨论:

①k为加号,由于是[L,k][k+1,R]中两种不同的合并两两组合相加,故dp[L][k]出现了(R-k-1)!次([k+1,R]的合并顺序总数),dp[k+1][R]出现了(k-L)!次([L,k]所有合并顺序的总数),对答案的贡献是dp[L][k]*(R-k-1)!+dp[k+1][R]*(k-L)!

②k为减号,同上,对答案的贡献是dp[L][k]*(R-k-1)!-dp[k+1][R]*(k-L)!

③k为乘号,[L,k][k+1,R]中两种不同的合并两两相乘,恰好符合乘法分配率,故对答案的贡献是dp[L][k]*dp[k+1][R]

至此,两个区间各自合并的顺序已考虑完毕,但合在一起,又出现了在大区间内合并顺序的问题。如【1+2】*【3+4】,先合并前面的加号与先合并后面的加号属于不同的合并方式,所以对于两个区间内各自的某种合并顺序,应乘上在大区间内所有不同的合并顺序总数,也就是组合数C(n,m),其中n表示两个区间内的符号个数总和,m表示其中一个区间内的符号个数总和。所以,组合数表也应预处理出来。

对于组合数表的预处理,最好使用递推公式C(n,m)=C(n-1,m-1)+C(n-1,m)。

如何理解这条公式呢?对于C(n,m),分为取n和不取n两种情况,也就是C(n-1,m-1)和C(n-1,m),根据加法原理,直接相加即可。

#include<iostream>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#define FOR(i,x,y) for(int i=(x);i<=(y);i++)
#define DOR(i,x,y) for(int i=(x);i>=(y);i--)
#define P 1000000007
#define N 100
typedef long long LL;
using namespace std;
LL fac[N+3],C[N+3][N+3];
LL dp[N+3][N+3];
char op[N+3];
void init()
{
	LL f=1;
	FOR(i,0,100)  //阶乘 
	{
		fac[i]=f;
		f=f*(i+1)%P;
	}
	FOR(i,0,100)  //组合数 
	{
		C[i][0]=1;
		FOR(j,1,i)
			C[i][j]=(C[i-1][j]+C[i-1][j-1])%P;
	}
	return;
}

int main()
{
	init();
	int n;
	while(scanf("%d",&n)!=EOF)
	{
		FOR(i,1,n)scanf("%lld",&dp[i][i]);
		scanf("%s",op+1);
		FOR(l,2,n)
			FOR(L,1,n-l+1)
			{
				int R=L+l-1;
				dp[L][R]=0;    //数  [L,k]      [k+1,R] 
				FOR(k,L,R-1)   //符号[L,k-1] k [k+1,R-1]
				{
					if(op[k]=='+')dp[L][R]=(dp[L][R]+(dp[L][k]*fac[R-k-1]+dp[k+1][R]*fac[k-L])%P*C[l-2][k-L])%P;
					if(op[k]=='-')dp[L][R]=(dp[L][R]+(dp[L][k]*fac[R-k-1]-dp[k+1][R]*fac[k-L])%P*C[l-2][k-L])%P;
				    if(op[k]=='*')dp[L][R]=(dp[L][R]+(dp[L][k]*dp[k+1][R])%P*C[l-2][k-L])%P;
				}
			}                   //题目要求保证答案非负 
	    printf("%lld\n",(dp[1][n]+P)%P);
	}
    return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值