https://www.bnuoj.com/v3/contest_show.php?cid=9148#problem/I

【题意】

给定n个操作数和n-1个操作符,组成一个数学式子。每次可以选择两个相邻操作数及中间的操作符进行运算,如a-b变成一个数(a-b),直到这个式子变成一个数。同一个初始式子可以进行不同的操作,最后的结果也可能不同。最后求不同操作得到结果的和(mod 1000 000 007)

对于两个操作,只要存在一步是不同的,这两个操作就被认为是不同的。

【思路】

总体思路是区间dp,对于dp[i][j],枚举k(i<=k<j),考察dp[i][k]和dp[k+1][j]

具体做的时候可以分别考虑*,+,-三种运算,用组合数学;

还有一种非常非常巧妙的办法:数学期望!

组合数学

比较明显的区间dp,令dp[l][r]为闭区间[l,r]的所有可能的结果和,考虑最后一个符号的位置k,k必须在l,r之间,则l≤k<r,dp[l][r]=Σ{dp[l][k]?dp[k+1][r]}*(r-l-1)!/[(k-l)!(r-k-1)!],其中(r-l-1)!/[(k-l)!(r-k-1)!]表示从左区间和右区间选择符号的不同方法总数(把左右区间看成整体,那么符号的选择在整体间也有顺序,内部的顺序不用管,那是子问题需要考虑的),相当于(k-l)个0和(r-k-1)个1放一起的不同排列方法总数。

对花括号里面的‘?‘分为三种情况:

(1)‘+‘  假设左区间有x种可能的方法,右区间有y种可能的方法,由于分配律的存在,左边的所有结果和会重复计算y次,右边的所有结果和会重复计算x次,而左边共(k-l)个符号,右边共(r-k-1)个符号,所以合并后的答案dp[l][r]=dp[l][k]*(r-k-1)!+dp[k+1][r]*(k-l)!

(2)‘-‘   与‘+‘类似

(3)‘*‘   由分配律,合并后的答案dp[l][r]=dp[l][k]*dp[k+1][r]

数学期望

如果将这个过程随机化,即每次等概率地选取相邻两项合并,

dp[i][j]为随机合并第i个到第j个数字这一段的表达式之后结果的期望

根据期望的线性可加性,状态转移方程为

dp[i][j]=(sigma_(k=i~j-1)(dp[i][k]?dp[k+1][j]))/(j-i),

其中"?"表示第k个数与第k+1个数之间的运算符,

那么dp[1][n]即为随机合并整个表达式之后结果的期望,

乘上方案数(n-1)!即为所求的总和,

由于是取模意义下的运算,转移方程中的除法要用逆元代替,

复杂度O(n^3)。

【Accepted】

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 #include<string>
 5 #include<algorithm>
 6 #include<cmath>
 7 
 8 using namespace std;
 9 typedef long long ll;
10 const int maxn=1e2+5;
11 const ll mod=1e9+7;
12 ll a[maxn];
13 char op[maxn];
14 ll dp[maxn][maxn];
15 ll fpow(ll x,ll n)
16 {
17     ll res=1LL;
18     while(n)
19     {
20         if(n&1)
21         {
22             res=(res*x)%mod;
23         }
24         x=(x*x)%mod;
25         n>>=1;
26     }
27     return res;
28 }
29 ll fact[maxn];
30 ll nfact[maxn];
31 int n;
32 void init()
33 {
34     fact[0]=1LL;
35     for(int i=1;i<maxn;i++)
36     {
37         fact[i]=(fact[i-1]*(ll)i)%mod;
38     }
39     nfact[0]=1LL;
40     for(int i=1;i<maxn;i++)
41     {
42         nfact[i]=fpow(fact[i],mod-2);
43     }
44 }
45 int main()
46 {
47     init();
48     while(~scanf("%d",&n))
49     {
50         memset(dp,0,sizeof(dp));
51         for(int i=0;i<n;i++)
52         {
53             cin>>a[i];    
54         }
55         scanf("%s",op);
56         for(int i=0;i<n;i++)
57         {
58             dp[i][i]=a[i];
59         }
60         for(int l=1;l<n;l++)
61             for(int i=0;i+l<n;i++)
62             {
63                 int j=i+l;
64                 for(int k=i;k<j;k++)
65                 {
66                     ll add;
67                     if(op[k]=='+')
68                     {
69                         add=(dp[i][k]*fact[j-1-k]%mod+dp[k+1][j]*fact[k-i]%mod)%mod;
70                     }
71                     else if(op[k]=='-')
72                     {
73                         add=(dp[i][k]*fact[j-1-k]%mod-dp[k+1][j]*fact[k-i]%mod+mod)%mod;
74                     }
75                     else
76                     {
77                         add=(dp[i][k]*dp[k+1][j]%mod)%mod;
78                     }
79                     add=(add*fact[l-1]%mod*nfact[j-1-k]%mod*nfact[k-i]%mod)%mod;
80                     dp[i][j]=(dp[i][j]+add)%mod;    
81                 }
82             }
83         cout<<dp[0][n-1]<<endl;
84         
85     }
86     return 0;
87 }
区间dp+组合数

相关文章: