【洛谷3648】[APIO2014] 序列分割(斜率優化DP)
阿新 • • 發佈:2018-11-19
大致題意: 你可以對一個序列進行\(k\)次分割,每次得分為兩個塊元素和的乘積,求總得分的最大值。
區間\(DPor\)斜率優化\(DP\)
這題目第一眼看上去感覺很明顯是區間\(DP\)。
但是,一看資料範圍,\(n\le100000\),這是要上天的節奏!
不過,再看\(m\le200\),比較顯然應該是\(O(nm)\)的時間複雜度。
實際上,這題的確是可以用斜率優化\(DP\)來做到\(O(nm)\)的。
推性質
首先,我們要知道一個性質:將一個區間進行若干次分割,分割的順序是不影響最後的總得分的。
證明如下:
設要對一個區間進行\(2\)次分割,則分割完後有\(3\)
則總共有兩種分割順序,得到的總得分分別為\(a_1·(a_2+a_3)+a_2·a_3\)和\((a_1+a_2)·a_3+a_1·a_2\)。
實際上,這兩個式子化簡後皆為\(a_1a_2+a_1a_3+a_2a_3\)。
而多次分割其實是同理的。
狀態轉移
這樣一來,就不難想到針對這種問題的常見套路:設\(f_{i,j}\)表示在前\(i\)個數中割\(j\)次得到的最大總得分。
狀態轉移方程如下:
\[f_{i,j}=f_{x,j-1}+sum_x*(sum_i-sum_x)\]
這應該是比較好理解的吧,就相當於列舉一個割的位置,總得分為該區間前半部分割\(j-1\) 次的最大得分加上這兩部分元素和的乘積。
對於這種式子,比較顯然可以斜率優化。
將原式展開得:
\[f_{x,j-1}-sum_x*sum_x=-sum_x*sum_i+f_{i,j}\]
要注意在轉移過程中可能會出現分母為\(0\)的情況,則需要給此時的斜率賦值為\(-INF\)。
由於要記方案,所以再用一個\(g\)陣列記錄下從哪個元素轉移而來即可。
程式碼
#include<bits/stdc++.h> #define max(x,y) ((x)>(y)?(x):(y)) #define min(x,y) ((x)<(y)?(x):(y)) #define Gmax(x,y) (x<(y)&&(x=(y))) #define Gmin(x,y) (x>(y)&&(x=(y))) #define abs(x) ((x)<0?-(x):(x)) #define swap(x,y) (x^=y^=x^=y) #define uint unsigned int #define LL long long #define ull unsigned long long #define INF 1e18 #define N 100000 #define M 200 using namespace std; int n,m,sum[N+5]; class Class_FIO { private: #define Fsize 100000 #define tc() (A==B&&(B=(A=Fin)+fread(Fin,1,Fsize,stdin),A==B)?EOF:*A++) #define pc(ch) (void)(FoutSize<Fsize?Fout[FoutSize++]=ch:(fwrite(Fout,1,FoutSize,stdout),Fout[(FoutSize=0)++]=ch)) int f,FoutSize,Top;char ch,Fin[Fsize],*A,*B,Fout[Fsize],Stack[Fsize]; public: Class_FIO() {A=B=Fin;} inline void read(int &x) {x=0,f=1;while(!isdigit(ch=tc())) f=ch^'-'?1:-1;while(x=(x<<3)+(x<<1)+(ch&15),isdigit(ch=tc()));x*=f;} inline void read_digit(int &x) {while(!isdigit(x=tc()));x&=15;} inline void readc(char &x) {while(isspace(x=tc()));} inline void reads(string &x) {x="";while(isspace(ch=tc()));while(x+=ch,!isspace(ch=tc())&&~ch);} inline void write(LL x) {if(!x) return pc('0');x<0&&(pc('-'),x=-x);while(x) Stack[++Top]=x%10+48,x/=10;while(Top) pc(Stack[Top--]);} inline void writec(char x) {pc(x);} inline void writes(string x) {for(register int i=0,len=x.length();i<len;++i) pc(x[i]);} inline void clear() {fwrite(Fout,1,FoutSize,stdout),FoutSize=0;} }F; class Class_SlopeDP { private: #define A(x) (f[x][j-1]-1LL*sum[x]*sum[x]) #define B(x) (-1LL*sum[x]) #define S(x,y) (B(x)^B(y)?1.0*(A(y)-A(x))/(B(y)-B(x)):-INF)//對於分母為0的情況,返回-INF #define Slope (1LL*sum[i]) int q[N+5],g[N+5][M+5];LL f[N+5][M+5]; inline void PrintStep(int x,int y) {y&&(PrintStep(g[x][y-1],y-1),F.write(x),F.writec(' '),0);}//輸出方案 public: inline void Solve()//DP轉移 { register int i,j,H,T; for(j=1;j<=m;++j) { for(q[H=T=1]=0,i=1;i<=n;++i)//每次記得清空佇列 { while(H<T&&S(q[H],q[H+1])<=Slope) ++H; f[i][j]=f[g[i][j]=q[H]][j-1]+1LL*sum[q[H]]*(sum[i]-sum[q[H]]); while(H<T&&S(q[T],i)<=S(q[T-1],q[T])) --T; q[++T]=i; } } } inline void Print() {F.write(f[n][m]),F.writec('\n'),PrintStep(g[n][m],m);} }SlopeDP; int main() { register int i; for(F.read(n),F.read(m),i=1;i<=n;++i) F.read(sum[i]),sum[i]+=sum[i-1]; return SlopeDP.Solve(),SlopeDP.Print(),F.clear(),0; }