1. 程式人生 > >牛客第9場-E 概率dp

牛客第9場-E 概率dp

把概率dp當數學題做推公式,也是我太菜了。最重要是我還津津有味推了一下午。

剛一開始我想到列舉對的題數,從n到0跑一遍,預處理出來全部發生的概率pp。然後通過

當 對的題數為n時 :  序列全部連續       得分為     pp * n^m.

當對的題數為n-1時:  我們要判斷錯的那個題在什麼位置   得分為累積和: 令i從1到n,  pp*(100-p[i])/p[i] * [(i-1)^m+(n-i)^m]  

當對的題數為n-2時: 類比得分為   令i從1到n,j從i+1到n, pp*(100-p[i])/p[i]*(100-p[j])/p[j] *[(i-1)^m+(j-1-i)^m+(n-j)^m]

.................

當對的題為0時   得分  也為0。

後來仔細想想這不就是把所有的情況都算了一遍嗎,類似於概率論求期望。當n為1000時,時間複雜度為(2^n),這是萬萬不可以的。看到大家用時不多,所以我又想到這可能是個規律題。又瘋狂開始找遞推公式,一找就是一下午。

步入正題:

首先我們要把100的逆元處理出來,畢竟題意中給的給的是p[i],而這件事發生的概率為p[i]/100.

然後我們列舉到每個題的位置時它的得分。不斷用一層迴圈遍歷這個位置之前的狀態。把每一種情況都考慮進去。

程式碼如下:

#include<bits/stdc++.h>
#define maxn 1005
#define mod 1000000007
typedef long long  ll;
using namespace std;
ll p[maxn],f[maxn];
ll _pow(ll a,ll b){
    ll ans=1;
  while(b){
    if(b&1)  ans=ans*a%mod;
     a=a*a%mod;
        b=b>>1;
  }
  return ans%mod;
}

int main(){
  int n,m;
    ll inv = _pow(100,mod-2);
    scanf("%d %d",&n,&m);
    for(int i=1;i<=n;i++)
         scanf("%lld",&p[i]);
    for(int i=1;i<=n;i++)
    {
        f[i]=f[i-1]*(100-p[i])%mod;
        f[i]=f[i]*inv%mod;
        ll pb=1,sum;
        for(int j=1;j<=i;j++)
         {
           sum=j;
           pb=pb*p[i-j+1]%mod;
           pb=pb*inv%mod;
           ll tmp = pb*(100-p[i-j])%mod;
           tmp=tmp*inv%mod;
           ll a;
           if(i==j)  a=0;
           else
              a=f[i-j-1];
           f[i]=(f[i]+tmp*(_pow(sum,m)+a)%mod)%mod;
         }
    }
    printf("%d\n",f[n]);
    return 0;
}

題目