3 3 2 1 -+ 5 1 4 6 8 3 +*-*
2999999689
这题是一道很好的区间dp,看完解题报告后才会做,先用状态dp[i][j]表示区间[i,j]里的和,用A[i]表示1到i的阶乘,c[i][j]表示组合数。那么我们在区间[i,j]里枚举k,(i<=k<j),如果当前第k个是乘号,那么根据乘法分配律,t=(dp[i][k]*dp[k+1][j])%MOD;,如果当前第k个是加或减,那么t=dp[i][k]*t=(dp[i][k]*A[j-k-1]+dp[k+1][j]*A[k-i])%MOD;因为选择的这个加号或减号两端的运算是互不干扰的,而一边的运算次数为另一边的组合数,所以有这样的状态方程,另外还要乘上一个c[j-i-1][k-i],因为我们确定的是两边各自的运算,但是我们可以这样的顺序取:左边先算乘法,然后右边算加法,再左边算减法...相当于安排左边符号运算在总运算个数的次序。还有注意(a+MOD)%MOD要在最后才能用,前面不能用,只能用a%MOD,不然会使结果改变。
#include<iostream> #include<stdio.h> #include<stdlib.h> #include<string.h> #include<math.h> #include<vector> #include<map> #include<set> #include<queue> #include<stack> #include<string> #include<algorithm> using namespace std; #define ll long long #define MOD 1000000007 char s[106]; ll a[106],dp[106][106],A[106],c[106][106]; void init() { int i,j; A[0]=1; A[1]=1; A[2]=2; for(i=3;i<=100;i++){ A[i]=(A[i-1]*(ll)i)%MOD; } for(i=1;i<=100;i++)c[i][0]=1; for(i=1;i<=100;i++){ for(j=1;j<=i;j++){ if(i==j)c[i][j]=1; else if(i>j) c[i][j]=(c[i-1][j]+c[i-1][j-1])%MOD; } } } int main() { int n,m,i,j,T,len,k; ll t; init(); while(scanf("%d",&n)!=EOF) { for(i=1;i<=n;i++){ scanf("%lld",&a[i]); dp[i][i]=a[i]; } scanf("%s",s+1); for(i=1;i<=n-1;i++){ if(s[i]=='+'){ dp[i][i+1]=(a[i]+a[i+1])%MOD; } else if(s[i]=='-'){ dp[i][i+1]=(a[i]-a[i+1])%MOD; } else if(s[i]=='*') dp[i][i+1]=(a[i]*a[i+1])%MOD; /*!!!!!!!!!!!!!!!!!*/ } for(len=3;len<=n;len++){ for(i=1;i+len-1<=n;i++){ j=i+len-1; dp[i][j]=0; for(k=i;k<j;k++){ t=0; if(s[k]=='*'){ t=(dp[i][k]*dp[k+1][j])%MOD; } else if(s[k]=='+'){ t=(dp[i][k]*A[j-k-1]+dp[k+1][j]*A[k-i])%MOD; } else if(s[k]=='-'){ t=(dp[i][k]*A[j-k-1]-dp[k+1][j]*A[k-i])%MOD; } dp[i][j]=(dp[i][j]+t*c[j-i-1][k-i])%MOD; } } } printf("%lld\n",(dp[1][n]+MOD)%MOD); } return 0; }