【区间dp】hdu5396 Expression

http://acm.hdu.edu.cn/showproblem.php?pid=5396


现在有n个(≤100)数字和n-1个操作,操作只有”+ - *”三种,并且顺序是a1 op1 a2 op2 a3 ⋯ an。
然后小明每一次随便选两个相邻的数和他们中间的符号,然后计算答案并且把那两个数擦掉换成新的数字。
执行n-1次后会得到一个数字,现在请问对于所有可能的操作顺序最后得到的结果,求和是多少?


思路:

dp[i][j]为第i个数字到第j个数字这段区间所有可能结果之和

考虑[i, j]这段区间内最后计算的符号为第k个符号,那么就应该是[i, k]这段区间的所有结果加减乘[k+1, j]这段区间的所有结果。

假设[i,k]区间的结果分别是A1,A2,A3...[k, j+1]区间的结果分别是B1,B2,B3....

那么dp[i][k] = A1 + A2 + A3 .... 同理 dp[k+1][j] = B1 + B2 + B3

当sign[k]为乘号时,结果分别是A1*B1,A1*B2.....A2*B1,A2*B2.......,由乘法结合律得:结果之和为dp[i][k] * dp[k+1][j]

当sign[k]为加减号时,结果分别为A1±B1,A1±B2.....A2±B1,A2±B2.......,那么结果之和为dp[i][k] * num[k+1][j] + dp[k+1][j] * num[i][k]

其中num[i][j]表示区间[i, j]合并完成有多少种顺序,其实也就等于区间长度的阶乘。

对于每一种[i, j]区间的结果,我们需要吧[i, k]区间的操作插到[k+1, j]区间的操作时间序列中,方法共有H(j - k, k - i) = C(j - i - 1, k - i)种

(对于同一结果,先执行左边操作或先执行右边操作虽然不影响算式结果但都要计算到答案中去)

于是对于每一个k,对于[i, j]区间的贡献是 sigma(计算结果) * 算出每个结果的方案数


注意最终答案要+mod再%mod防止出现负数(我也不知道为什么orz


#include <cstdio>
#include <iostream>
#include <cstring>

using namespace std;

const int mod = 1e9 + 7;

int n, si[105];
int f[105][105];
int mul[105], C[105][105];

int num(int i, int k, int j)
{
	int res = ( (long long)mul[k - i] * mul[j - k - 1] ) % mod;
	res = ( (long long)res * C[j - i - 1][k - i] ) % mod;
	return res;
}

int dp(int L, int R)
{
	if (L == R || f[L][R]) return f[L][R];
	for (int k = L; k < R; ++ k) {
		long long tmp;
		if (si[k] == 3) tmp = ( (long long)dp(L, k) * dp(k + 1, R) ) % mod;
		else {
			tmp = (long long) dp(L, k) * mul[R - k - 1];
			if (si[k] == 1) tmp += (long long) dp(k + 1, R) * mul[k - L];
			else tmp -= (long long) dp(k + 1, R) * mul[k - L];
			tmp %= mod;
		}
		tmp *= C[R - L - 1][k - L]; tmp %= mod;
		f[L][R] = ( (long long) f[L][R] + tmp ) % mod;
	}
	//printf("f[%d][%d] = %d\n", L, R, f[L][R]);
	return f[L][R];
}

int main()
{
	mul[0] = mul[1] = 1;
 	for (int i = 2; i <= 100; ++ i) {
		mul[i] = ( (long long)mul[i - 1] * i ) % mod;
	}
	for (int i = 0; i <= 100; ++ i) C[i][0] = 1; 
	for (int i = 1; i <= 100; ++ i) {
		for (int j = 1; j <= i; ++ j) {
			C[i][j] = C[i - 1][j - 1] + C[i - 1][j];
			if (C[i][j] > mod) C[i][j] %= mod;
		//	printf("C(%d, %d) = %d\n", i, j, C[i][j]);
		}
	}
	while (~scanf("%d", &n)) {
		memset(f, 0, sizeof(f));
		for (int i = 1; i <= n; ++ i) 
			scanf("%d", &f[i][i]);
		char c;
		for (int i = 1; i < n; ++ i) {
			scanf(" %c", &c); 
			if (c == '+') si[i] = 1;
			else if (c == '-') si[i] = 2;
			else si[i] = 3;
		} 
		printf("%d\n", (dp(1, n) + mod) % mod);
	}
	return 0;
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值