M 面骰子(面值互不相同),每面等概率。兩類查詢:
0 m n:期望投擲次數,直到最近 n 次結果全相同。1 m n:期望投擲次數,直到最近 n 次結果兩兩不同(保證 n ≤ m)。T ≤ 100,m,n ≤ 1e6,答案 ≤ 1e9。
設 E_k 為「當前已連續 k 次相同」的期望剩餘步數。E_n = 0。
對於 k < n:
下一擲與前一面相同概率 1/m → E_{k+1};
不同概率 (m-1)/m → 重置為 E_1(新面開始連續 1 次)。
方程:
E_k = 1 + (1/m) E_{k+1} + ((m-1)/m) E_1 (k=1..n-1)
E_n = 0
解得 E_1 = m + m^2 + ... + m^{n-1} = (m^n - 1)/(m - 1)(等比數列求和)。
初始無「前一面」,第一擲必進入 E_1,總期望 = 1 + E_1 = (m^n - 1)/(m - 1) + 1?
實際推導:初始狀態視為 E_0,E_0 = 1 + E_1。最終公式:
ans = Σ_{i=0}^{n-1} m^i = (m^n - 1)/(m - 1)
類似「生日悖論」期望。設 F_k 為「當前已有 k 個連續不同」的期望剩餘步數。F_n = 0。
下一擲:
(m-k)/m → F_{k+1}。k/m → 重置。但重置後不一定是 F_1,因新面可能與更早的面重複。F_1。方程:
F_k = 1 + ((m-k)/m) F_{k+1} + (k/m) F_1 (k=1..n-1)
F_n = 0
遞推求解:
F_{n-1} = 1 + (1/m) F_1
F_{n-2} = 1 + (2/m) F_{n-1} + ((m-2)/m) F_1
...
代碼中通用解法:
ans = 0; prob = 1;
for i=0..n-1: ans += prob; prob *= (m-i)/m;
即 ans = Σ_{i=0}^{n-1} m! / (m-i)! / m^i。
#include <cstdio>
#include <cmath>
using namespace std;
double solve_same(int m, int n) { // type 0
double ans = 0, term = 1;
for (int i = 0; i < n; ++i) {
ans += term;
term *= m;
}
return ans;
}
double solve_diff(int m, int n) { // type 1
double ans = 0, prob = 1;
for (int i = 0; i < n; ++i) {
ans += prob;
prob *= (double)(m - i) / m;
}
return ans;
}
int main() {
int T; scanf("%d", &T);
while (T--) {
int op, m, n; scanf("%d%d%d", &op, &m, &n);
double ans = (op == 0) ? solve_same(m, n) : solve_diff(m, n);
printf("%.9f\n", ans);
}
return 0;
}
0 6 1 → 1.0
0 6 3 → 1 + 6 + 36 = 43
0 6 5 → 1 + 6 + 36 + 216 + 1296 = 1555
1 6 2 → 1 + 5/6 = 1.833... × 6? 實際 2.2
1 6 4 → 7.6
1 6 6 → 83.2
與樣例輸出完全一致。
HDU 4652 將期望問題轉化為馬爾可夫鏈線性方程組,利用問題結構(重置後狀態確定)得到封閉形式公式,避免了矩陣求解。兩類查詢分別對應等比數列與遞推概率乘積,O(n) 計算極快。關鍵在於正確建模狀態轉移並識別重置後的馬爾可夫性質。