[APIO2014] 序列分割(斜率优化dp)
problem
luogu-P3648
你正在玩一個關于長度為 nnn 的非負整數序列的游戲。這個游戲中你需要把序列分成 k+1k+1k+1 個非空的塊。
為了得到 k+1k+1k+1 塊,你需要重復下面的操作 kkk 次:
- 選擇一個有超過一個元素的塊(初始時你只有一塊,即整個序列)
- 選擇兩個相鄰元素把這個塊從中間分開,得到兩個非空的塊。
- 每次操作后你將獲得那兩個新產生的塊的元素和的乘積的分數。
你想要最大化最后的總得分。
輸出最后得分以及劃分的任一方案。
solution
前提結論:得分與切的先后順序無關。
假設要把 abcabcabc 切成 a∣b∣ca\mid b\mid ca∣b∣c。
- 先切 a∣bca\mid bca∣bc,再切 a∣b∣ca\mid b\mid ca∣b∣c,ans=a?(b+c)+b?c=ab+ac+bcans=a*(b+c)+b*c=ab+ac+bcans=a?(b+c)+b?c=ab+ac+bc。
- 先切 ab∣cab\mid cab∣c,再切 a∣b∣ca\mid b\mid ca∣b∣c,ans=(a+b)?c+a?b=ac+bc+abans=(a+b)*c+a*b=ac+bc+abans=(a+b)?c+a?b=ac+bc+ab。
這樣就可以從左到右 dpdpdp 了。記 sumi=∑j=1iaisum_i=\sum_{j=1}^ia_isumi?=∑j=1i?ai?,即元素分數前綴和。
設 f(i,j):f(i,j):f(i,j): 前 iii 個數切成 jjj 段的最大得分。
則 f(i,j)=max?k<i{f(k,j?1)+sum(k)?(sum(i)?sum(k))}f(i,j)=\max_{k<i}\Big\{f(k,j-1)+sum(k)*\big(sum(i)-sum(k)\big)\Big\}f(i,j)=maxk<i?{f(k,j?1)+sum(k)?(sum(i)?sum(k))}。
jjj 只跟 j?1j-1j?1 有關,考慮提到最外面循環,內層簡寫 f(i,j?1)=f(i)f(i,j-1)=f(i)f(i,j?1)=f(i)。
假設 k1<k2k_1<k_2k1?<k2?,且 k1k_1k1? 決策優于 k2k_2k2?。
則必定滿足:
f(k1)+sumk1?sumi?sumk12>f(k2)+sumk2?sumi?sumk22f(k_1)+sum_{k_1}*sum_i-sum_{k_1}^2>f(k_2)+sum_{k_2}*sum_i-sum_{k_2}^2 f(k1?)+sumk1???sumi??sumk1?2?>f(k2?)+sumk2???sumi??sumk2?2?
f(k1)?sumk12?f(k2)+sumk22>(sumk2?sumk1)sumif(k_1)-sum_{k_1}^2-f(k_2)+sum_{k_2}^2>(sum_{k_2}-sum_{k_1})sum_i f(k1?)?sumk1?2??f(k2?)+sumk2?2?>(sumk2???sumk1??)sumi?
sumsumsum 是前綴和數組,所以 sumk2≥sumk1sum_{k_2}\ge sum_{k_1}sumk2??≥sumk1??,具有單調性。
(f(k1)?sumk12)?(f(k2)?sumk22)(?sumk1)?(?sumk2)>sum(i)\frac{\big(f(k_1)-sum_{k_1}^2\big)-\big(f(k_2)-sum_{k_2}^2\big)}{(-sum_{k_1})-(-sum_{k_2})}>sum(i) (?sumk1??)?(?sumk2??)(f(k1?)?sumk1?2?)?(f(k2?)?sumk2?2?)?>sum(i)
標準斜率優化的式子,因為 O(nk)O(nk)O(nk) 無法再接受一個 log?\loglog ,所以我最熱愛的李超樹就被 pass\text{pass}pass 掉了。
斜率優化想必最頭疼的就是用隊列維護首尾彈出時的大小于符號的定向問題。
網上一堆凸包圖形,對于我這種平面幾何感幾乎為零的蒟蒻而言簡直就是天方夜譚。
所以這里寫一種判斷方法吧,不保證正確性
-
隊首的彈出很簡單,因為上面的斜率優化式子已經化出來了,直接 slope(q[head],q[head+1])slope(q[head],q[head+1])slope(q[head],q[head+1]) 和 sum(i)sum(i)sum(i) 比較。
如果 ≤sum(i)\le sum(i)≤sum(i) 說明 k2k_2k2? 即后者 q[head+1]q[head+1]q[head+1] 更優。
就需要彈隊首。
-
隊尾的彈出較為復雜,考慮的是 slope(q[tail?1],q[tail])slope(q[tail-1],q[tail])slope(q[tail?1],q[tail]) 和 slope(q[tail],i)slope(q[tail],i)slope(q[tail],i)。
準確而言是考慮 q[tail]q[tail]q[tail] 可不可能做隊首。
那么當現在的隊尾在后面某個枚舉位置時變成了隊首,
就必須要滿足 slope(q[tail?1],q[tail])≤sumpslope(q[tail-1],q[tail])\le sum_pslope(q[tail?1],q[tail])≤sump?,這樣才能彈出 q[tail?1]q[tail-1]q[tail?1]。
-
假設彈隊尾的條件是 slope(q[tail?1],q[tail])≤slope(q[tail],i)slope(q[tail-1],q[tail])\le slope(q[tail],i)slope(q[tail?1],q[tail])≤slope(q[tail],i)。
顯然存在 slope(q[tail?1],q[tail])≤sump<slope(q[tail],i)slope(q[tail-1],q[tail])\le sum_p<slope(q[tail],i)slope(q[tail?1],q[tail])≤sump?<slope(q[tail],i) 的可能性。
所以這么彈可能把答案取值點給彈出去。
-
假設彈隊尾的條件是 slope(q[tail?1],q[tail])≥slope(q[tail],i)slope(q[tail-1],q[tail])\ge slope(q[tail],i)slope(q[tail?1],q[tail])≥slope(q[tail],i)。
因為 slope(q[tail],i)≤slope(q[tail?1],q[tail])≤sumpslope(q[tail],i)\le slope(q[tail-1],q[tail])\le sum_pslope(q[tail],i)≤slope(q[tail?1],q[tail])≤sump?,
所以 q[tail?1]q[tail-1]q[tail?1] 被 q[tail]q[tail]q[tail] 彈出后,馬上 q[tail]q[tail]q[tail] 就會被 iii 彈出去。
那么 q[tail]q[tail]q[tail] 永遠都不可能當隊首。
綜上,我們確定了隊尾判斷的符號。
-
注意:前面的表述據說明 sumisum_isumi? 不是嚴格遞增的,存在平臺,所以遇到 sumi=sumjsum_i=sum_jsumi?=sumj? 返回斜率極小值即可。
code
#include <bits/stdc++.h> using namespace std; #define int long long #define maxn 100005 int n, k, d; int a[maxn], sum[maxn], q[maxn]; int g[maxn][205], f[maxn][205]; double slope( int x, int y ) {if( sum[x] == sum[y] ) return -1e18;return (f[x][d] - sum[x] * sum[x] - f[y][d] + sum[y] * sum[y]) * 1.0 / (sum[y] - sum[x]); } void print( int n, int d ) {if( d == 1 ) return;print( g[n][d], d - 1 );printf( "%d ", g[n][d] ); } signed main() {scanf( "%lld %lld", &n, &k );for( int i = 1;i <= n;i ++ ) scanf( "%lld", &a[i] );for( int i = 1;i <= n;i ++ ) sum[i] = sum[i - 1] + a[i];for( d = 1;d <= k;d ++ ) {int head = 1, tail = 0;for( int i = 1;i <= n;i ++ ) {while( head < tail and slope( q[head], q[head + 1] ) <= sum[i] ) head ++;f[i][d + 1] = f[q[head]][d] + sum[q[head]] * ( sum[i] - sum[q[head]] );g[i][d + 1] = q[head];while( head < tail and slope( q[tail - 1], q[tail] ) >= slope( q[tail], i ) ) tail --;q[++ tail] = i;}}printf( "%lld\n", f[n][d] );print( n, d );return 0; }總結
以上是生活随笔為你收集整理的[APIO2014] 序列分割(斜率优化dp)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 自然语言处理的未来之路(周明老师,CCF
- 下一篇: 路由器怎么连接手机如何用手机设置路由器连