• 【区间dp+组合数+数学期望】Expression


    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+组合数
     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 ll md=1e9+7;
    11 ll fpow(ll x,ll n)
    12 {
    13     ll res=1LL;
    14     while(n)
    15     {
    16         if(n&1)
    17         {
    18             res=(res*x)%md;
    19         }
    20         x=(x*x)%md;
    21         n>>=1;
    22     }
    23     return res;
    24 }
    25 const int maxn=1e2+3;
    26 ll a[maxn];
    27 char op[maxn];
    28 ll dp[maxn][maxn];
    29 ll fact[maxn];
    30 void init()
    31 {
    32     fact[0]=1LL;
    33     for(int i=1;i<maxn;i++)
    34     {
    35         fact[i]=(fact[i-1]*(ll)i)%md;
    36     }
    37 }
    38 int n;
    39 int main()
    40 {
    41     init();
    42     while(~scanf("%d",&n))
    43     {
    44         memset(dp,0,sizeof(dp));
    45         for(int i=0;i<n;i++)
    46         {
    47             cin>>a[i];
    48         }
    49         scanf("%s",op);
    50         for(int i=0;i<n;i++)
    51         {
    52             dp[i][i]=a[i];
    53         }
    54         for(int l=1;l<n;l++)
    55         {
    56             for(int i=0;i+l<n;i++)
    57             {
    58                 int j=i+l;
    59                 for(int k=i;k<j;k++)
    60                 {
    61                     ll add;
    62                     if(op[k]=='+')
    63                     {
    64                         add=(dp[i][k]+dp[k+1][j])%md;
    65                     }
    66                     else if(op[k]=='-')
    67                     {
    68                         add=(dp[i][k]-dp[k+1][j]+md)%md;
    69                     }
    70                     else
    71                     {
    72                         add=(dp[i][k]*dp[k+1][j])%md;
    73                     }
    74                     dp[i][j]=(dp[i][j]+add)%md;
    75                 }
    76                 dp[i][j]=(dp[i][j]*fpow(l,md-2))%md;
    77             }
    78         }
    79         ll ans=(dp[0][n-1]*fact[n-1])%md;
    80         cout<<ans<<endl;
    81     }
    82     return 0;
    83 }
    区间dp+数学期望(随机化过程,期望的线性可加性)

    【知识点】

    1. 取模意义下的除法不能直接除,要用逆元代替。根据费马小定理,a^(p-1)=1(mod p),所以a^(p-2)=(1/a)(mod p),所以a的逆元就是a^p-2。通常p是一个很大的素数,如经常见到的1e9+7,所以要用快速幂。

    快速幂模板

    fastpow
     1 ll fpow(ll x,ll n)
     2 {
     3     ll res=1LL;
     4     while(n)
     5     {
     6         if(n&1)
     7         {
     8             res=(res*x)%mod;
     9         }
    10         x=(x*x)%mod;
    11         n>>=1;
    12     }
    13     return res;
    14 }

    2. 组合数C(i,j)有两种算法:
    第一种:

    fac(i+j)*nfac(i)*nfac(j) 
    //其中nfac(i)=fastpow(fac(i),mod-2)

    第二种:

    C[0][0]=1;
        for (int i=0;i<201;i++)
        {
            C[i][0]=1;
            for (int j=1;j<=i;j++)
            {
                C[i][j]=(C[i-1][j-1]+C[i-1][j])%md;
            }
        }

    3. 在取模运算下,要注意减法,a-b通常要写成(a-b+md)%md

    总之注意不要算出负数

  • 相关阅读:
    JavaScript字符串和字符数组
    JavaScript数组&类数组转换
    JavaScript判断值是否是NaN
    JavaScript中七种数据类型·中·一
    QRcode.js 生成二维码
    你不知道的JavasScript上篇·第五章·原型·下
    你不知道的JavasScript上篇·第五章·原型·上
    你不知道的JavasScript上篇·第四章·混合对象·类
    你不知道的JavasScript上篇·第三章·对象
    Vue2.x之父子组件数据传递
  • 原文地址:https://www.cnblogs.com/itcsl/p/7123468.html
Copyright © 2020-2023  润新知