P5516-[MtOI2019]小鈴的煩惱【期望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}
我們觀察一下這個式子,其實等價於
p
i
−
p
i
−
1
=
p
i
+
1
−
p
i
p_i-p_{i-1}=p_{i+1}-p_{i}
pi−pi−1=pi+1−pi表示
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
2
i
(
n
−
i
)
n
(
n
−
1
)
\frac{1}{\frac{2i(n-i)}{n(n-1)}}
n(n−1)2i(n−i)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}
因為知道
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(n−i)n(n−1)+2ii−1fi−1+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)}
(−2ii−1fi−1)+(fi)+(−2ii+1fi+1)=2i(n−i)n(n−1)
又有
f
n
=
0
f_n=0
fn=0
這樣問題就變成了
n
−
1
n-1
n−1個線性方程的方程組,因為每個方程組只有三個元,高斯消元會有大量多餘步驟,所以我們直接手動先消掉
f
i
−
1
f_{i-1}
fi−1再返回來消掉
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);
}