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 }