1. 程式人生 > >5396 (區間dp +組合計數)

5396 (區間dp +組合計數)

題目大意:

給你一個n然後是n個數。 然後是n-1個操作符,操作符是插入在兩個數字之間的。 由於你不同的運算順序,會產生不同的結果。

比如:

1 + 1 * 2 有兩種  (1+1)*2   或者  1+(1*2)

1 *  2 * 3  也是兩種即使結果是一樣的  (1*2)*3  或者 1*(2*3)

問這所有不同的組合加起來的和對 1e9+7取餘是多少。

這個其實就是區間DP了

dp[i][j] 代表的是區間  i 到 j 的和

列舉dp[i][j] 之間所有的子區間

假如是乘法:

t = dp[i][k] * dp[k+1][j];

這個其實可以直接算出來的:

假設我們dp[i][k] 裡面所有的值是 (x1+x2+x3...xn) == dp[i][k]

假設我們dp[k+1][j] 裡面所有的值是 (y1+y2+y3...yn) == dp[k+1][j]

dp[i][k] * dp[k+1][j] == (x1+x2+...xn) * (y1+y2+y3...yn) == x1*y1+x1y*y2......xn*yn 其實和所有不同結果相乘出來是一樣的

假如是加法或者減法:

我們表示階乘 i為A[i].

t = dp[i][k]*A[j-k-1] + dp[k+1][j]*A[k-i];

其實這裡我們想一下。區間 dp[i][k] 需要加上多少次?

我們需要加的次數就是另一半區間的所有組合數,另一半區間有多少種組合方式我們就要加上多少個。

因為他們之間可以相互組成不同的種類。同理另一半也是。

最後的時候我們要乘上一個組合數。

假設組合數為C[i][j].

為什麼要乘組合數:

因為 假如我們k 分割了兩個運算式子   【 1+(2*3)  】 + 【 1+(3*4) 】

雖然說我們左右兩邊的式子運算順序已經確定了,但是我們總的運算順序還是不確定的, 比如我們算完(2*3) 直接去算(3*4)也是不同的結果

dp[i][j] = dp[i][j] + t*C[j-i-1][k-i]

這個其實就是從總的運算子(j-i-1)(減去了第k個的運算子)箇中選取(k-i)個進行運算。

因為我們選取數達到 k-i的時候,另外我們還需要保持左右兩邊運算的相對順序。

就比如說:左邊有a 個運算子, 右邊有b個運算子。

我們從 a+b個位置中選取 a位置個放a個的運算子。其餘的只能放另一邊的的運算子了。因為我們左右兩邊的相對順是不變的。

程式碼:

#include <bits/stdc++.h>
using namespace std;
const int maxn=1e5+7;
const int inf=0x3f3f3f3f;
typedef long long ll;
ll C[120][102],A[120];
const int mod=1e9+7;
int n;
ll dp[120][120];
char s[120];
void init()
{
    A[0] = 1;
    for(int i=1; i<=100; i++)
        A[i] = (A[i-1] * i)%mod;
    C[0][0] = 1;
    for(int i=1; i<=100; i++)
    {
        C[i][0] = 1;
        for(int j=1; j<=i; j++)
            C[i][j] = (C[i-1][j-1] + C[i-1][j])%mod;
    }
}
int main()
{
    #ifndef ONLINE_JUDGE
        freopen("in.txt","r",stdin);
        freopen("out.txt","w",stdout);
    #endif
    init();
    while(scanf("%d",&n)!=EOF)
    {
        memset(dp,0,sizeof(dp));
        for(int i=1;i<=n;i++)
        {
           scanf("%lld",&dp[i][i]);
        }
        scanf("%s",s+1);
        for(int L=2;L<=n;L++)
        {
            for(int i=1;i<=n-L+1;i++)
            {
                int j=i+L-1;
                dp[i][j]=0;
                for(int k=i;k<j;k++)
                {
                    ll tmp;
                    if(s[k]=='*')
                    {
                        tmp=(dp[i][k]*dp[k+1][j])%mod;
                    }
                    if(s[k]=='+')
                    {
                        tmp=(dp[i][k]*A[j-k-1]+dp[k+1][j]*A[k-i])%mod;
                    }
                    if(s[k]=='-')
                    {
                        tmp=(dp[i][k]*A[j-k-1]-dp[k+1][j]*A[k-i])%mod;
                    }
                    dp[i][j]=(dp[i][j]+tmp*C[j-i-1][k-i])%mod;
                }
            }
        }
        printf("%lld\n",(dp[1][n]+mod)%mod);
    }
    return 0;
}