loj6171/bzoj4899 記憶的輪廊(期望dp+優化)
題目:
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[1View Code][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 }
方法三:
一個很神奇的二分套路(詳見王欽石《淺析一類二分方法》)
這是一個限制段數的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+優化)