【題意】:
一篇文章n個字符,每打一個字符(包括退格)需要1個單位時(shí)間。時(shí)不時(shí)可以用t個時(shí)間去檢查錯誤發(fā)現(xiàn)錯誤,用退格鍵退到第一個錯誤的地方,然后重新打。給出每個字符錯誤的概率,問打完這篇文章所需最小時(shí)間的期望。
【題解】:設(shè)dp[i]表示前i個字符已經(jīng)正確打完,從第i+1個字符到結(jié)束所需的最小時(shí)間的期望。
顯然有dp[n] = 0,然后最終答案就是dp[0]了。
枚舉從i+1個字符開始打,打完k個字符就檢查一次。
dp[i] = min{(t+k) + q[i+1]*(dp[i]+k) + p[i+1]q[i+2]*(dp[i+1]+k-1) + …… + p[i+1]p[i+2]……q[i+k]*(dp[i+k-1]+1) + p[i+1]p[i+2]……p[i+k]*(dp[i+k]) } ,其中 1 <= k <= n - i。
p[i] 是正確的概率,q[i] 是錯誤的概率。
轉(zhuǎn)移的意思是:一次過打k個字符,然后用 t 時(shí)間去檢查,最后用退格鍵退回到第一個錯誤的位置。
注意這個轉(zhuǎn)移方程,很容易發(fā)現(xiàn)這是O(n*n*n)的。
從哪個開始打起 i
一次打了多少個 j
第一個錯誤的位置 k
O(n*n*n)的做法明顯會TLE的,但是利用數(shù)學(xué)知識,我們可以化簡掉一維。最終的時(shí)間復(fù)雜度為O(n*n)。
【代碼】:
1 #include "iostream"
2 #include "cstdio"
3 #include "cstring"
4 using namespace std;
5 #define maxn 3050
6 int n, t;
7 double dp[maxn], p[maxn], q[maxn];
8
9 void solve() {
10 dp[n] = 0;
11 for(int i = n - 1; i >= 0; i--) {
12 double c = p[i+1];
13 double sum = t + 1 + q[i+1] + dp[i+1] * p[i+1];
14 dp[i] = sum;
15 for(int j = 2; j <= n - i; j++) {
16 c *= p[i+j];
17 sum = sum + 2 - c + c * dp[i+j] - c * dp[i+j-1];
18 dp[i] = min(dp[i], sum);
19 }
20 dp[i] /= p[i+1];
21 }
22 printf("%.6f\n", dp[0]);
23 }
24
25 int main() {
26 while(~scanf("%d%d", &n, &t)) {
27 for(int i = 1; i <= n; i++) {
28 scanf("%lf", &q[i]);
29 p[i] = 1 - q[i];
30 }
31 solve();
32 }
33 return 0;
34 }
35