题目链接:
http://acm.hdu.edu.cn/showproblem.php?pid=5396
解题思路:
题目大意:
题目大意:有n个数,和n-1个符号('+','-','*')形成一个表达式,现在问对于不同的运算序列,得到的结果的总和是多少(结果为非负整数,对1e9+7取余)
dp[i][j]记录在区间l到r内的各种不同的运算序列的结果的和。
官方题解:
记dpl,r表示l,r这段数能形成的答案总和。
枚举最后一步操作k,如果是乘法,答案为dpl,k∗dpk+1,r,由于分配率这个会乘开来。如果是加法那么是dpl,r∗(r−k−1)!+dpk+1,r∗(k−l)!,即要乘上右边k+1,r这些数所有可行的方案数,减法同理。最后乘上(k−lr−l−2),即把两边操作合起来的方案数。
答案为dp1,n。
算法思想:
首先长度len是1的时候,dp[i][i] = a[i]
之后dp[i][j] = ∑ (dp[i][k] 和 dp[k+1][j] 合并而成),k为[i,j]范围内的运算符号,那么分三种情况:1、'+'
对于dp[i][k]共有k-l个符号,也就是有(k-i)!种不同的运算序列,dp[k+1][j]有(j-k-1)!种运算序列,那么对于不同的运算序列得到的值应该累加,所以累加的和为dp[i][k]*(j-k-1)! + dp[k+1][j]*(k-i),同时还要注意,这种运算序列是符号j的左右两侧分开考虑的,如果交叉考虑,只要各自相对的运算顺序没变,那么值就不会变,但会形成新的一种运算方法,所以最终的值还要再乘以C(j-i-1 , k-i)。
2、'-'
和加法基本相同,只需要把加换成减。
3、'*'
对于乘法,我们需要的结果是左右表达式中的各种运算方式得到的值分别相乘然后累加和,这个结果就可以直接转化为dp[i][k]*dp[k+1][j],同样我们还需要对最终的结果乘以C(j-i-1 , k-i)
注意取余,最终的dp[0][n-1]就是结果
AC代码:
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
typedef long long ll;
const int MOD = 1e9+7;
const int maxn = 100;
ll c[maxn+5][maxn+5],f[maxn+5];
ll dp[maxn+5][maxn+5];
char op[maxn+5];
void init(){
for(int i = 0; i <= maxn; i++){
c[i][0] = c[i][i] = 1;
for(int j = 1; j <= i; j++)
c[i][j] = (c[i-1][j]+c[i-1][j-1])%MOD;
}
f[0] = 1;
for(int i = 1; i <= maxn; i++)
f[i] = f[i-1]*i%MOD;
}
int main () {
init ();
int n;
while (~scanf("%d",&n)) {
memset(dp, 0, sizeof(dp));
for (int i = 0; i < n; i++)
scanf("%d", &dp[i][i]);
getchar();
gets(op);
for (int i = n-1; i >= 0; i--) {
for (int j = i + 1; j < n; j++) {
int len = j - i - 1;
for (int k = i; k < j; k++) {
int l = k - i, r = j - k - 1;
ll ret = 0;
if (op[k] == '+') {
ret = (ret + dp[i][k] * f[r]) % MOD;
ret = (ret + dp[k+1][j] * f[l]) % MOD;
}
else if (op[k] == '-') {
ret = (ret + dp[i][k] * f[r]) % MOD;
ret = ((ret - dp[k+1][j] * f[l]) % MOD + MOD) % MOD;
}
else if (op[k] == '*')
ret = dp[i][k] * dp[k+1][j] % MOD;
dp[i][j] = (dp[i][j] + ret * c[len][l]) % MOD;
}
//printf("%d %d: %d\n", i, j, dp[i][j]);
}
}
printf("%lld\n", dp[0][n-1]);
}
return 0;
}