1. 程式人生 > >[APIO2014]序列分割 --- 斜率優化DP

[APIO2014]序列分割 --- 斜率優化DP

font char display img soft \n 復雜 AS cst

[APIO2014]序列分割

題目大意:

你正在玩一個關於長度為\(n\)的非負整數序列的遊戲。這個遊戲中你需要把序列分成\(k+1\)個非空的塊。為了得到\(k+1\)塊,你需要重復下面的操作\(k\)次:

選擇一個有超過一個元素的塊(初始時你只有一塊,即整個序列)

選擇兩個相鄰元素把這個塊從中間分開,得到兩個非空的塊。

每次操作後你將獲得那兩個新產生的塊的元素和的乘積的分數。你想要最大化最後的總得分。

\(n<=10^{5},k<=200\)

首先劃分完\(k\)塊後,發現非常像線性DP模型

自然地想,是不是分數跟劃的順序無關?

可以證明是的(歸納法)

那麽,設

\(dp(i,j)\)表示枚舉到了\(i\),第\(1...i\)切了幾刀的最大收益。

有\(dp(i,j)=max(dp(k,j-1)+sum[k]*(sum[i]-sum[k]))(1<=k<=i-1)\)

那麽展開式子,化為斜率優化的式子:

\(-dp(k,j-1)=sum[k]*sum[i]-sum[k]^{2}-dp(i,j)\)

其中\(k\)為\(sum[k]\),單調遞增

其中\(x\)為\(sum[i]\),單調遞增

要使\(dp(i,j)\)最大,因此維護下凸包,那麽可以使用單調隊列

空間滾一下就好

空間復雜度:\(O(n)\)(忽略記錄決策點)

時間復雜度:\(O(nk)\)

註:被宏定義坑了很久。。。

技術分享圖片
#include<cstdio>
#include
<cstring> #define sid 100050 #define dd double #define ll long long #define ri register int using namespace std; #define getchar() *S ++ char RR[30000005], *S = RR; inline int read(){ int p = 0, w = 1; char c = getchar(); while(c > 9 || c < 0) { if(c == -) w = -1; c
= getchar(); } while(c >= 0 && c <= 9) { p = p * 10 + c - 0; c = getchar(); } return p * w; } ll sum[sid], dp[2][sid]; int lst[205][sid], q[sid], n, k; bool now = 0, pre = 1; #define x(g) sum[(g)] #define y(g) (sum[(g)]*sum[(g)]-dp[pre][(g)]) inline dd s(int i, int j) { if(x(i) == x(j)) return -1e18; return (dd)(y(i) - y(j)) / (dd)(x(i) - x(j)); } int main() { fread(RR, 1, sizeof(RR), stdin); n = read(); k = read(); for(ri i = 1; i <= n; i ++) sum[i] = sum[i - 1] + read(); for(ri j = 1; j <= k; j ++) { int fr = 1, to = 1; now ^= 1; pre ^= 1; for(ri i = 1; i <= n; i ++) { while(fr + 1 <= to && s(q[fr], q[fr + 1]) <= sum[i]) fr ++; dp[now][i] = dp[pre][q[fr]] + sum[q[fr]] * (sum[i] - sum[q[fr]]); lst[j][i]=q[fr]; while(fr + 1 <= to && s(q[to - 1], q[to]) >= s(q[to], i)) to --; q[++ to] = i; } } printf("%lld\n",dp[now][n]); int e = n; for(ri i = k; i >= 1; i --) { e = lst[i][e]; printf("%d ", e); } return 0; }
序列分割

[APIO2014]序列分割 --- 斜率優化DP