题目:http://acm.hdu.edu.cn/showproblem.php?pid=5396
题意:给出n个数,每相邻的两个数之间有一个运算符号,“+”, “-” 或 “*”,你进行n - 1次操作,每次操作选出一个符号c和它左右的两个数a和b,计算出 M = a c b,将得到的数代替 a c b 得到新的式子,直到最后式子为一个数。求按不同顺序选择符号得到的数的和。
思路:
dp[ i ][ j ] 表示序列为 [ i, j ] 时得到的数的和, j - i 大的答案由 j - i 小的答案递推得到,区间dp。
将 [ i, j ] 分为两部分,k为连接两部分的符号的位置:
1)加法 :dp[i][k] * A[j - k - 1] + dp[k + 1][j] * A[k - i] // 阶乘 A [j - k - 1]对应右边得到的数有多少个
2)减法 :dp[i][k] * A[j - k - 1] - dp[k + 1][j] * A[k - i]
3)乘法: dp[i][k] * dp[k + 1][j]
还需要注意,假设左边得到一个数的符号顺序为f1,f2,f3,右边得到一个数的符号顺序为g1,g2,(k 为 [ i, j ] 的最后一个操作符号),总的符号顺序可能是f1,f2,g1,f3,g2,也可以是g1,f1,f2,f3,g2,共C(5,3)种可能,所以还要乘C(j - i - 1,k - i)
计算C[][]的时候要用组合数递推公式,因为除法不能取模。
还要注意取模
#include <stdio.h>
#include <string.h>
using namespace std;
typedef long long ll;
int a[105];
ll A[105], dp[105][105] ,C[105][105];
const ll MOD = 1000000007;
char c[105];
int main()
{
#ifdef LOCAL
freopen("in.txt", "r", stdin);
#endif
int n;
A[0] = 1;
for(int i = 1; i <= 103; i++)
{
A[i] = A[i - 1] * i % MOD;
}
C[0][0] = 1;
for(int i = 1; i <= 103; i++)
{
C[i][0] = C[i][i] = 1;
for(int j = 1; j < i; j++)
{
C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % MOD;
}
}
while(scanf("%d", &n) != EOF)
{
memset(dp, 0, sizeof(dp));
for(int i = 1; i <= n; i++)
{
scanf("%d", &a[i]);
dp[i][i] = a[i];
}
scanf(" %s", &c[1]);
for(int jj = 1; jj <= n; jj++)
{
for(int i = 1; i + jj <= n; i++)
{
int j = i + jj;
for(int k = i; k < j; k++)
{
int cur = 0;
if(c[k] == '+')
{
cur = dp[i][k] * A[j - k - 1] % MOD + dp[k + 1][j] * A[k - i] % MOD;
}
else if (c[k] == '-')
{
cur = dp[i][k] * A[j - k - 1] % MOD - dp[k + 1][j] * A[k - i] % MOD;
}
else
{
cur = dp[i][k] * dp[k + 1][j] % MOD;
}
cur %= MOD;
dp[i][j] += cur * C[j - i - 1][k - i];
dp[i][j] %= MOD;
}
}
}
printf("%I64d\n", (dp[1][n]+ MOD) % MOD);
}
return 0;
}