原题: http://acm.hdu.edu.cn/showproblem.php?pid=3480
题意:
n个数排序后,分成多段,每一段是最大值减最小值的平方,求和的最小值。
解析:
老套路:
表示前面j个数分成i段的最小值,有 设 , 优于 ,有
再结合这道题进行分析:
- 问: 首先是二维的问题,第一次写会比较懵逼。在做dp[i]的时候,队列中的元素应该以dp[i-1]作为参考,但是队列中的元素又是我们做dp[i]的同时塞进去的。
答: 我们不需要提前得出dp[i-1],在做dp[i]的同时维护队列是没有问题的。如果都是以dp[i-1]为参考,取队首元素更新dp[i]应该没有问题。其次,我们进队列的时候也是按照dp[i-1]为参考,所以不会有问题。 - 再看状态转移方程式,除数为 ,显然可以为0。所以我们不能直接除,而要转换成乘的方式进行比较。
#include<bits/stdc++.h>
using namespace std;
#define LL long long
int dp[10009][10009];
int s[10009];
int Q[10009],L,R;
inline double check(int i,int x,int y){
double Z=-(double)(dp[i-1][x]-dp[i-1][y]-s[y+1]*s[y+1]+s[x+1]*s[x+1]);
return Z;
}
int main(){
int t;scanf("%d",&t);
int ca=0;
while(t--){
int n,m;scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)scanf("%d",s+i);
sort(s+1,s+1+n);
for(int i=1;i<=n;i++)dp[1][i]=(s[i]-s[1])*(s[i]-s[1]);
for(int i=2;i<=m;i++){
Q[L=1,R=1]=i-1;//dp[i-1][i-1]
for(int j=i;j<=n;j++){
while(R>L){
double X=check(i,Q[L],Q[L+1]),Y=2.0*s[j]*(s[Q[L+1]+1]-s[Q[L]+1]);
if(X<=Y)L++;
else break;
}
dp[i][j]=dp[i-1][Q[L]]+(s[j]-s[Q[L]+1])*(s[j]-s[Q[L]+1]);
while(R>L){
double X=check(i,Q[R-1],Q[R])*(s[j+1]-s[Q[R]+1]),Y=check(i,Q[R],j)*(s[Q[R]+1]-s[Q[R-1]+1]);
if(X>=Y)R--;
else break;
}
Q[++R]=j;
}
}
printf("Case %d: %d\n",++ca,dp[m][n]);
}
}
四边形优化
再来试试新学的四边形优化
显然
满足四边形不等式
所以 可以尝试四边形优化, 或者 。
因为我们可以很快的得出 的情况,所以 应该是从小到大。所以选择 ,需要用到 的情况,所以第二维从大到小做即可。
#include<bits/stdc++.h>
using namespace std;
#define LL long long
int dp[5009][10009];
int best[5009][10009];
int s[10009];
int main(){
int t;scanf("%d",&t);
int ca=0;
while(t--){
int n,m;scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)scanf("%d",s+i);
sort(s+1,s+1+n);
for(int i=1;i<=n;i++)dp[1][i]=(s[i]-s[1])*(s[i]-s[1]),best[1][i]=1;
for(int i=2;i<=m;i++){
best[i][n+1]=n-1;
//使遍历到n-1,防止遗漏
for(int j=n;j>=i;j--){
int now=2e9,pos;
for(int k=best[i-1][j];k<=best[i][j+1];k++){
if(now>dp[i-1][k]+(s[j]-s[k+1])*(s[j]-s[k+1])){
now=dp[i-1][k]+(s[j]-s[k+1])*(s[j]-s[k+1]);
pos=k;
}
}
dp[i][j]=now;
best[i][j]=pos;
}
}
printf("Case %d: %d\n",++ca,dp[m][n]);
}
}