1. 程式人生 > 其它 >P5516-[MtOI2019]小鈴的煩惱【期望dp,線性消元】

P5516-[MtOI2019]小鈴的煩惱【期望dp,線性消元】

技術標籤:dp數學期望luoguMtOI期望dp線性消元

正題

題目連結:https://www.luogu.com.cn/problem/P5516


題目大意

n n n個字母的一個字串,每次隨機選取兩個不同的位置 ( x , y ) (x,y) (x,y)讓第 x x x個位置的字元變成第 y y y個位置的字元。

求期望多少次能夠把所有字元變成同一個。


解題思路

因為最終狀態很多,所以我們考慮列舉最終狀態,我們稱之為目標狀態。

p i p_i pi表示目標狀態的字元有 i i i個時,該狀態變為最終狀態的概率,那麼有 p i = i ( n − i ) n ( n − 1 ) p i − 1 + n ( n − 1 ) − 2 i ( n − i ) n ( n − 1 ) + i ( n − i ) n ( n − 1 ) p i + 1 ⇒ p i = p i − 1 + p i + 1 2 p_i=\frac{i(n-i)}{n(n-1)}p_{i-1}+\frac{n(n-1)-2i(n-i)}{n(n-1)}+\frac{i(n-i)}{n(n-1)}p_{i+1}\Rightarrow p_i=\frac{p_{i-1}+p_{i+1}}{2}

pi=n(n1)i(ni)pi1+n(n1)n(n1)2i(ni)+n(n1)i(ni)pi+1pi=2pi1+pi+1
我們觀察一下這個式子,其實等價於 p i − p i − 1 = p i + 1 − p i p_i-p_{i-1}=p_{i+1}-p_{i} pipi1=pi+1pi表示 p p p是一個等差數列,而 p 0 = 0 , p n = 1 p_{0}=0,p_{n}=1 p0=0,pn=1所以 p i = i n p_i=\frac{i}{n} pi=ni

然後設 f i f_i fi表示目標狀態有 i i i個且是最終狀態的情況下到達最終狀態的期望步數,而因為 數學期望=概率×步數

,和 單次期望= 1 單 次 概 率 \frac{1}{單次概率} 1

所以我們成功造成一次變化的期望次數是 1 2 i ( n − i ) n ( n − 1 ) \frac{1}{\frac{2i(n-i)}{n(n-1)}} n(n1)2i(ni)1,又因為多和少的概率是相等的,所以有方程
f i p i = p i n ( n − 1 ) 2 i ( n − i ) + p i + 1 f i + 1 + p i − 1 f i − 1 2 f_ip_i=p_i\frac{n(n-1)}{2i(n-i)}+\frac{p_{i+1}f_{i+1}+p_{i-1}f_{i-1}}{2}

fipi=pi2i(ni)n(n1)+2pi+1fi+1+pi1fi1
因為知道 p i = i n p_i=\frac{i}{n} pi=ni所以直接帶進去解出來就有
f i = n ( n − 1 ) 2 i ( n − i ) + i − 1 2 i f i − 1 + i + 1 2 i f i + 1 f_i=\frac{n(n-1)}{2i(n-i)}+\frac{i-1}{2i}f_{i-1}+\frac{i+1}{2i}f_{i+1} fi=2i(ni)n(n1)+2ii1fi1+2ii+1fi+1
換一個方式表達就是
( − i − 1 2 i f i − 1 ) + ( f i ) + ( − i + 1 2 i f i + 1 ) = n ( n − 1 ) 2 i ( n − i ) (-\frac{i-1}{2i}f_{i-1})+(f_i)+(-\frac{i+1}{2i}f_{i+1})=\frac{n(n-1)}{2i(n-i)} (2ii1fi1)+(fi)+(2ii+1fi+1)=2i(ni)n(n1)
又有 f n = 0 f_n=0 fn=0
這樣問題就變成了 n − 1 n-1 n1個線性方程的方程組,因為每個方程組只有三個元,高斯消元會有大量多餘步驟,所以我們直接手動先消掉 f i − 1 f_{i-1} fi1再返回來消掉 f i + 1 f_{i+1} fi+1就好了。

時間複雜度 O ( n ) O(n) O(n)


c o d e code code

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=2100;
int n,num[26];
double f[N],a[N],ans;
char s[N];
int main()
{
    scanf("%s",s+1);
    n=strlen(s+1);
    for(int i=1;i<=n;i++)
        num[s[i]-'A']++;
    for(int i=1;i<=n;i++)
        f[i]=n*(n-1)/(2.0*i*(n-i));
    a[1]=1;
    for(int i=1;i<n;i++){
        double r=-(i+1)/(2.0*i),l=-i/(2.0*(i+1));
        double k=-l/a[i];
        f[i+1]+=f[i]*k;
        a[i+1]=1+k*r;
    }
    f[n]=0;
    for(int i=n-1;i>=1;i--){
        double r=-(i+1)/(2.0*i);
        f[i]-=f[i+1]*r,f[i]=f[i]/a[i];
    }
    for(int i=0;i<26;i++)
        ans+=num[i]*f[num[i]]/(double)n;
    printf("%.1lf\n",ans);
}