1. 程式人生 > >loj6171/bzoj4899 記憶的輪廊(期望dp+優化)

loj6171/bzoj4899 記憶的輪廊(期望dp+優化)

get 答案 題目 bre ron 決策單調 重新 預處理 http

題目:

https://loj.ac/problem/6171

分析:

設dp[i][j]表示從第i個點出發(正確節點),還可以有j個存檔點(在i點使用一個存檔機會),走到終點n的期望步數

那麽技術分享

a[i][k]表示i點為存檔點,從i點走到k點(正確節點)的期望步數(中間沒有其它存檔點)

那麽a[i][j]可以遞推預處理出

技術分享

其中g[v]表示從一個錯誤節點v開始走,期望走g[v]步會讀檔

解方程可以解出技術分享

s[j-1]就是點j-1出去的所有錯誤兒子的g[v]之和

那麽接下來只要知道如何求g[v]就行了

技術分享

這個直接dfs一遍就行了

好,那麽現在我們的主dp就可以求解了技術分享

但是直接dp的復雜度是O(n^2p)的,這樣會TLE

方法一:

註意到這個dp的本質是把一個序列給分成p段,那麽其中某一段會不會很長呢?

我們會發現a的增長是非常快的,而最終的答案不會很大,所以也就是說當前的i的最優轉移j,不會離i太遠

所以通過計算可以發現這個距離step<=40

所以時間復雜度O(40n^2)

方法二:

考慮dp優化的慣用套路

容易得出此dp是決策單調的,也就是f(i)<=f(i+1)

那麽就可以決策單調優化O(nplogn)

具體的就維護一個隊列,隊列裏每個元素存著[l,r,p]表示區間l~r,當前最優決策是p

每次從隊頭取出最優策略,將此次新的決策從隊尾開始放入並合並區間

技術分享
 1         dp[1
][n]=0.0; 2 for(int now=2;now<=number;++now) 3 { 4 int head=1,tail=1; 5 q[1]={1,n-1,n}; 6 for(int i=n-1;i>=1;--i) 7 { 8 while(head<tail&&q[head].l>i) ++head; 9 dp[now][i]=cal(now-1
,i,q[head].p); 10 while(head<tail&&cal(now-1,q[tail].r,i)<cal(now-1,q[tail].r,q[tail].p)) --tail; 11 int position=find(now,q[tail].l,q[tail].r,i,q[tail].p); 12 if(position) 13 { 14 q[tail+1]={1,position,i}; 15 q[tail].l=position+1; 16 if(q[tail].l>q[tail].r) ++head; 17 ++tail; 18 } 19 } 20 }
View Code

方法三:

一個很神奇的二分套路(詳見王欽石《淺析一類二分方法》)

這是一個限制段數的dp,我們把它寫成不限制段數的情況

技術分享

然後我們去二分一個常數C,使得式子變成這樣

技術分享

這裏的C表示每次重新開一段所需要的代價

很明顯,C越大,最優情況下分的段數就越少,C越小,最優情況下分的段數就越多

所以我們可以二分C,對於每個C,進行dp

通過n->pre[n]->pre[pre[n]]->...->1,我們可以知道存了多少次檔,當存檔數恰好等於p的時候,此時對應的劃分方案就是讀檔p次時候的最優解,就是將dp的最優值減去C*p

但是有個trick,王欽石論文裏也提到了

就是可能當前eps下,並沒有哪個C會使得我恰好讀了p次檔,即某個C情況下,我讀了p-1次檔,在C-eps情況下,我讀了p+1次檔,就是沒有讀p次檔

這時候有個結論就是C-eps時,我讀p+1次檔這個情況下也必定有我讀p次檔的解,此時原本答案是dp-(p+1)*C,現在這樣改成讀p次檔之後,答案就是dp-p*C

這樣復雜度是O(n^2logA)

當然這裏的dp可以優化,但不過預處理的時候O(n^2)是跑不掉的,所以再優化也不會低於O(n^2)的復雜度

技術分享
 1         int minnum=m+1;
 2         while (l+eps<=r)
 3         {
 4             long double mid=(l+r)/2;
 5             int num=check(mid);
 6             long double sum=0;
 7             for(int now=n;now!=1;now=pre[now]) sum+=w[pre[now]][now];
 8             if (num<=p)
 9             {
10                 if (num==p)
11                 {
12                     ans=sum;
13                     break;
14                 };
15                 r=mid-eps;
16             }
17             else
18             {
19                 if(num<=minnum)
20                 {
21                     ans=sum+(num-p)*mid;
22                     minnum=num;
23                 }
24                 l=mid+eps;
25             }
26         }
View Code

loj6171/bzoj4899 記憶的輪廊(期望dp+優化)