题目链接:
题目描述:
给出n个数字,n-1个符号。问不同的操作顺序得到的所有结果相加是多少?
解题思路:
n取值范围[1,100],肯定不能强行搜索。比赛的时候队友在这个题目上卡了好久,扎扎赛后决定把这个补了来拯救我快要卡哭了的队友。可以看出这个是明显的区间dp题目,dp[l][i]代表[l,r]这个区间内不同组合结果的总和。然后我们就要讨论怎么进行状态转移咯。由于'*', '-', '+'运算的性质,我们要分别对他们进行讨论。
'*':有乘法的分配律可知,dp[l][r] = dp[l][k]*dp[k+1][r];
'-'/'+':加减法就没有分配律咯,所以要分别在两边乘上一个阶乘,让两边的不同结果一一对应匹配。
最后还要再乘上一个组合数,因为左右两边操作符在之前是有顺序的,但是两边合并以后两边的操作符在保证了自己的顺序后是可以混合的,也就是要在r - l + 1个位置里挑出k-l个位置。
1 #include <cstdio> 2 #include <cstring> 3 #include <iostream> 4 #include <algorithm> 5 using namespace std; 6 7 const int maxn = 110; 8 const int mod = 1000000007; 9 typedef long long LL; 10 LL dp[maxn][maxn], C[maxn], b[maxn]; 11 char op[maxn]; 12 //组合数用费马小定理取余 13 LL Pow (LL x) 14 { 15 LL res = 1; 16 LL n = mod - 2; 17 while (n) 18 { 19 if (n % 2) 20 res = (res * x) % mod; 21 x = (x * x) % mod; 22 n /= 2; 23 } 24 return res; 25 } 26 27 void init () 28 {//阶乘表 29 C[0] = b[0] = 1; 30 for (int i=1; i<maxn; i++) 31 { 32 C[i] = (C[i-1] * i) % mod; 33 b[i] = Pow (C[i]); 34 } 35 } 36 37 LL Sum (LL x, LL y, char p) 38 { 39 if (p == '+') 40 return (x + y + mod) % mod; 41 if (p == '-') 42 return (x - y + mod) % mod; 43 if (p == '*') 44 return (x * y + mod) % mod; 45 } 46 47 int main () 48 { 49 int n; 50 init (); 51 while (scanf ("%d", &n) != EOF) 52 { 53 memset (dp, 0, sizeof(dp)); 54 for (int i=0; i<n; i++) 55 { 56 scanf ("%lld", &dp[i][i]); 57 dp[i][i] = (dp[i][i] + mod) % mod; 58 } 59 scanf ("%s", op+1); 60 61 for (int j=0; j<n; j++) 62 {//区间终点 63 for (int i=j-1; i>=0; i--)//区间起点 64 for (int k=j; k>i; k--) 65 {//枚举区间内最后一个运算的符号 66 LL s1, s2; 67 68 if (op[k] == '*') 69 { 70 s1 = dp[i][k-1]; 71 s2 = dp[k][j]; 72 } 73 else 74 {//加减法要乘两边符号的贡献 75 s1 = (dp[i][k-1] * C[j-k] + mod) % mod; 76 s2 = (dp[k][j] * C[k-i-1] + mod) % mod; 77 } 78 79 s1 = (Sum(s1, s2, op[k]) * (C[j-i-1] * b[j-k] % mod * b[k-i-1] % mod) + mod) % mod; 80 //取余的时候一定要把组合数括起来,要不会死的很惨的, 81 dp[i][j] = (dp[i][j] + s1 + mod) % mod; 82 } 83 } 84 printf ("%lld ", dp[0][n-1]); 85 } 86 return 0; 87 }