题目大意:给定一个长度为n的序列,至多将序列分成m+1段,每段序列都有权值,权值为序列内两个数两两相乘之和。m<=n<=1000.
分析:令w[i,j]表示区间[i,j]中两两乘积之和,f[i][j]表示前j个数分成i段的最小值。
f[i][j]=f[i-1][k]+w[k+1,j]
w[k+1,j]可以转换为w[1,j]-w[1,k]-sum[k]*(sum[j]-sum[k])
其中sum[j]表示前j个数的前缀和。
f[i][j]=f[i-1][k]+w[j]-w[k]-sum[k]*(sum[j]-sum[k])
令y=f[i-1][k]+w[j]-w[k]+sum[k]^2,x=sum[k],b=sum[j],g=f[i][j],则有:
y-bx=g
此为直线方程,b为定值,要求g最小,即为直线的截距最小。平面上有若干点(x,y),这些点是由各个决策点产生的。而将直线从下往上平移,它接触到的第一个点即为最佳决策点。因为斜率b是上升的,所以,下一阶段的直线方程斜率更高,于是最佳决策点一定形成了下凸包序列。
#include<iostream> #include<cstdio> #include<cstring> #define MAXN 1005 #define LL long long int LL f[MAXN][MAXN],w[MAXN],sum[MAXN]; #define FZ(i,p) (f[i-1][p]-w[p]+sum[p]*sum[p]) int n,m,num[MAXN]; int que[MAXN],head,tail; #define MAXZ (1LL<<22) bool turnup(int i,int p1,int p2,int p3) //p1>p2>p3 { LL y1=FZ(i,p1); LL x1=sum[p1]; LL y2=FZ(i,p2); LL x2=sum[p2]; LL y3=FZ(i,p3); LL x3=sum[p3]; if((x2-x3)*(y1-y2)>(x1-x2)*(y2-y3))return 1; else return 0; } int main() { while(scanf("%d%d",&n,&m)&&(n||m)) { memset(sum,0,sizeof sum); memset(w,0,sizeof w); m++; for(int i=1;i<=n;i++) {scanf("%d",&num[i]); sum[i]=sum[i-1]+num[i]; w[i]=w[i-1]+sum[i-1]*num[i]; } for(int i=1;i<=n;i++) f[1][i]=w[i]; for(int i=2;i<=m;i++) { //f[i-1][i-1]=0; head=tail=1; que[tail++]=i-1; for(int j=i;j<=n;j++) { while(head<tail-1&&FZ(i,que[head+1])-FZ(i,que[head])<sum[j]*(sum[que[head+1]]-sum[que[head]]))head++; int k=que[head]; f[i][j]=f[i-1][k]+w[j]-w[k]-sum[k]*(sum[j]-sum[k]); while(head<tail-1&&turnup(i,j,que[tail-1],que[tail-2])==0) tail--; que[tail++]=j; } } /* for(int i=1;i<=m;i++) {for(int j=1;j<=n;j++) printf("%I64d ",f[i][j]); printf(" "); } */ printf("%I64d ",f[m][n]); } }
本题也可以用平行四边形优化。
f[i][j]=f[i-1][k]+w[k+1,j]
w[k+1,j]很明显满足区间包含性质和平行四边形性质,所以f[i][j]也满足平行四边形性质,所以设s[i][j]表示f[i][j]的最佳决策点。s[i-1][j]<=s[i][j]<=s[i][j+1]。
从常识角度来思考,也是比较好想的。
如果数组元素不变,分段数增加,肯定分界点会往两边扩展,若分段数减少,则分界点会往中间靠拢,所以s[i][j]<=s[i+1][j]
如果分段数不变,数的个数增加(右边补充进来),分界点应该往右微调;如果数的个数减少(从右边剔除),分界点应该往左微调,所以有s[i][j-1]<=s[i][j]
所以s[i-1][j]<=s[i][j]<=s[i][j+1]
1 #include<iostream> 2 #include<cstring> 3 #include<cstdio> 4 #define MAXN 1005 5 #define MAXM 1005 6 #define LL long long int 7 #define min(a,b) ((a)<(b)?(a):(b)) 8 LL f[MAXN][MAXM],sum[MAXN],w[MAXN],s[MAXN][MAXM]; 9 int num[MAXN],t,n,m; 10 using namespace std; 11 void pre() 12 { 13 for(int i=1;i<=n;i++) 14 sum[i]=sum[i-1]+num[i]; 15 for(int i=1;i<=n;i++) 16 w[i]=w[i-1]+sum[i-1]*num[i]; 17 } 18 int main() 19 { 20 while(scanf("%d%d",&n,&m)&&(n||m)) 21 { 22 m++; 23 for(int i=1;i<=n;i++) 24 scanf("%d",&num[i]); 25 memset(f,0x3f,sizeof f); 26 pre(); 27 for(int i=1;i<=n;i++) 28 {for(int j=i;j<=n;j++) 29 s[i][j]=i-1; 30 } 31 for(int i=1;i<=n;i++) 32 s[i][m+1]=i-1; 33 for(int i=1;i<=n;i++) 34 f[i][i]=0; 35 for(int j=1;j<=n;j++) 36 f[j][1]=w[j]; 37 38 for(int i=1;i<=n;i++) 39 for(int j=min(m,i);j>=1;j--) 40 { 41 for(int k=s[i][j+1];k>=s[i-1][j];k--) 42 { 43 LL temp=f[k][j-1]+w[i]-w[k]-sum[k]*(sum[i]-sum[k]); 44 if(f[i][j]>temp) 45 {f[i][j]=temp; 46 s[i][j]=k; 47 } 48 } 49 } 50 printf("%I64d ",f[n][m]); 51 } 52 }