1. 程式人生 > >序列(DP)(組合數)

序列(DP)(組合數)

計算 進行 一個 限定 處理 mem isp class 組合數

這是一個DP題。

我們設\(f[i][j][k]\)表示\(i\)序列長度中放入了\(j\)個元素,其中\(k\)是限定的眾數的個數;狀態轉移方程是
\[f[k][i][j]=f[k][i-1][j-1]+f[k][i-1][j]-f[k][i-k-1][j-1]\]

前面的兩個相加應該比較好理解,也就是我們考慮從上一個長度轉移過來,新加入的那個數是原先已經加入過的元素還是沒有加入過的元素。

後面減去是因為要去掉不合法的情況——如果新加入的這個數出現了k+1次那麽就顯然是不合法情況。(註:\(i-(i-k-1)=k+1\))

因為是多組數據,但是數據範圍並不大,而且我們的答案是一步一步推出來的的,所以針對每次詢問都重新算一遍不如預處理方便。那麽我們就在詢問前進行預處理。

之後就是開一個g數組,\(g[i]\)表示的是眾數個數小於等於i的時候的個數。之後我們就可以用原先算過的sum數組來累加g的值了。

但是要註意的是,最後一維我們不確定是那些數,而我們又要計算序列種類個數+需要的是不下降的序列,所以我們可以想到是組合數。然後也是同樣的,考慮進行預處理。我們預處理從n個裏面選出來i個數就行了qwq,但是因為n的範圍很大,但我們需要的範圍只在m以內,所以預處理到m即可。

最後我們用出現次數乘上它的種類個數再除以總和就是期望了。但是註意因為我們g數組相當於是在計算前綴和(因為要算總數個數),所以最後要差分。

最後註意精度問題,我們最好使用long double。(但是因為long double占用16個字節,是int 類型的4倍啊qwq),一定要註意空間是否會炸。。。。。)

#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
int T,m,n;
long double C[255],sum[255][255][255],f[255][255][255],g[255];

int main(){
    C[0]=true;
    for(register int k=1;k<=251;++k){
        sum[k][0][0]=1;
        for(register int i=1;i<=251;++i){
            for(register int j=1;j<=i;++j){
                sum[k][i][j]=sum[k][i-1][j-1]+sum[k][i-1][j];
                if(k<i) sum[k][i][j]-=sum[k][i-k-1][j-1]; 
            }               
        }
    }
    while(cin>>m>>n){
        memset(g,0,sizeof(g)); 
        for(register int i=1;i<=m;++i)
            C[i]=C[i-1]*(n-i+1)/i;
        for(register int k=1;k<=m;++k)
            for(register int j=1;j<=m;++j)
                g[k]+=sum[k][m][j]*C[j];
        long double ans=0;
        for(register int k=1;k<=m;++k)
            ans+=k*(g[k]-g[k-1])/g[m];
        printf("%.4Lf\n",(double)ans);
    }
    return 0;
}

序列(DP)(組合數)