1. 程式人生 > >BZOJ 3675 [Apio2014]序列分割 (斜率優化DP)

BZOJ 3675 [Apio2014]序列分割 (斜率優化DP)

題目大意:讓你把序列切割k次,每次切割你能獲得 這一整塊兩側數字和的乘積 的分數,求最大的分數並輸出切割方案

神題= =

搞了半天也沒有想到切割順序竟然和答案無關...我太弱了

證明很簡單,就是乘法分配律,把式子展開就行了

定義$s_{i}$為序列$a$的字首和,定義$f[k][i]$表示第$k$次切割是在第$i$個位置的後面,$f[k][i]=max(f[k-1][j]+(s_{i}-s_{j})*(s_{n}-s_{i}))$

展開式子,移項,發現$x$遞增,斜率$k$也遞增,用佇列維護上凸包就行了

至於記錄方案,另開一個數組,記錄從哪轉移來的就行了

複雜度$O(nk)$

又沒長記性把$i$打成$j$了(捂臉)

 1 #include <cmath>
 2 #include <queue>
 3 #include <vector>
 4 #include <cstdio>
 5 #include <cstring>
 6 #include <algorithm>
 7 #define N1 101000
 8 #define M1 205
 9 #define ll long long
10 #define dd double  
11 #define uint unsigned int
12 #define idx(X) (X-'0')
13
using namespace std; 14 15 int gint() 16 { 17 ll ret=0;int fh=1;char c=getchar(); 18 while(c<'0'||c>'9'){if(c=='-')fh=-1;c=getchar();} 19 while(c>='0'&&c<='9'){ret=ret*10+c-'0';c=getchar();} 20 return ret*fh; 21 } 22 int n,K; 23 int a[N1]; 24 ll sa[N1],f[2][N1],x[N1],y[N1];
25 int fa[M1][N1]; 26 int que[N1],ret[N1]; 27 28 int main() 29 { 30 freopen("t2.in","r",stdin); 31 scanf("%d%d",&n,&K); 32 for(int i=1;i<=n;i++) 33 a[i]=gint(),sa[i]=sa[i-1]+a[i]; 34 int now=1,pst=0; 35 for(int k=1;k<=K;k++) 36 { 37 int hd=1,tl=0,j; 38 que[++tl]=0; 39 for(int i=1;i<n;i++) 40 { 41 while(hd+1<=tl&&(y[que[hd+1]]-y[que[hd]])>=-(x[que[hd+1]]-x[que[hd]])*sa[i]) 42 hd++; 43 j=que[hd]; 44 f[now][i]=f[pst][j]+(sa[i]-sa[j])*(sa[n]-sa[i]); 45 fa[k][i]=j; 46 x[i]=sa[i],y[i]=f[pst][i]-sa[i]*sa[n]; 47 while(hd+1<=tl&&(y[i]-y[que[tl-1]])*(x[que[tl]]-x[que[tl-1]])>=(y[que[tl]]-y[que[tl-1]])*(x[i]-x[que[tl-1]])) 48 tl--; 49 que[++tl]=i; 50 }swap(now,pst); 51 } 52 ll ans=0,id=0; 53 for(int i=1;i<n;i++) 54 if(f[pst][i]>ans) 55 ans=f[pst][i],id=i; 56 for(int k=K;k>=1;k--) 57 ret[k]=id,id=fa[k][id]; 58 printf("%lld\n",ans); 59 for(int k=1;k<=K;k++) 60 printf("%d ",ret[k]); 61 puts(""); 62 return 0; 63 }