在x数轴上,给出v个递增的正整数坐标({a_i}),请寻找p个点,使坐标到点的最短距离之和最小,(vleq 300,pleq 30)。
解
(注意,此题证明过于复杂,不能保证证明完全严谨和正确,如有看不懂,很有可能是打错了,请联系作者)
step1
从答案思考问题,发现必然是连续的几个点,管一段的坐标,这是区间划分模型,设(f[i][j])表示前j个坐标到前i个点的最短距离之和最小值,(w[i][j])为在第i个坐标到第j个坐标中选择一个点让第i个坐标到第j个坐标到这个点距离之和的最小值((s_i=sum_{j=1}^ia_j),即a的前缀和)。
边界:(f[i][i]=0,i=0,1,2,..,v)
答案:(f[p][v])
显然(w[i][j]=sum_{k=i}^j|a_k-a_m|(m=frac{i+j}{2})=[(a_j-a_m)+...+(a_m-a_m)+...)
(+(a_m-a_i)]=(a_j+...+a_{m+1})-(a_m+...+a_i)+[m-i+1-(j-m)])
(a_m=(s_j-s_m)-(s_m-s_{i-1})+(2m-i-j+1)a_m=s_j-2s_m+s_{i-1}+(2m-i-j+1)a_m)
于是我们可以实现维护出w,然后进行转移即可,时间复杂度(O(pv^2))
参考代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#define il inline
#define ri register
using namespace std;
int a[350],s[350],mid[350][350],
dp[35][350];
template<class free>
il free Min(free a,free b);
int main(){
int v,p;
scanf("%d%d",&v,&p);
for(int i(1);i<=v;++i)
scanf("%d",&a[i]),s[i]=s[i-1]+a[i];
for(int i(1),j,m;i<=v;++i)
for(j=i+1;j<=v;++j)
m=(i+j)/2,mid[i][j]=s[j]
-s[m]-(s[m]-s[i-1])+a[m]*(2*m-i+1-j);
memset(dp,1,sizeof(dp)),dp[0][0]=0;
for(int i(1),j,k;i<=p;++i)
for(dp[i][0]=0,j=1;j<=v;++j)
for(k=0;k<j;++k)
dp[i][j]=Min(dp[i][j],dp[i-1][k]+mid[k+1][j]);
printf("%d",dp[p][v]);
return 0;
}
template<class free>
il free Min(free a,free b){
return a<b?a:b;
}
setp2
但是如果你还有梦想,想要优化,二进制(拆分,压缩,倍增)显然不行,我们也没必要维护区间和,而栈与队列不适用,单调性维护平衡树,单调队列,双堆对顶,二分查找,离散化一个也不行,式子对于矩阵快速幂和斜率优化太过于复杂,考虑四边形不等式,关键在证明w满足四边形不等式
求证:w满足四边形不等式
证明:
现在要证明(i<j,w[i][j+1]+w[i+1][j]geq w[i][j]+w[i+1][j+1])
即证明(设各自中位数(m_1,m_2,m_3,m_4))
$s_{j+1}-2s_{m_1}+s_{i-1}+(2m_1-i-j)a_{m_1}+s_j-2s_{m_2}+s_{i}+(2m_2-i-j)a_{m_2}geq ( )s_j-2s_{m_3}+s_{i-1}+(2m_3-i-j+1)a_{m_3}+s_{j+1}-2s_{m_4}+s_{i}+(2m_4-i-j-1)a_{m_4}$
即证明
$-2s_{m_1}+(2m_1-i-j)a_{m_1}-2s_{m_2}+(2m_2-i-j)a_{m_2}geq ( )-2s_{m_3}+(2m_3-i-j+1)a_{m_3}-2s_{m_4}+(2m_4-i-j-1)a_{m_4}$
已知(m_1=frac{i+j+1}{2},m_2=frac{i+j+1}{2},m_3=frac{i+j}{2},m_4=frac{i+j+2}{2})
I. (i+jin)奇
有(m_1=m_2=m_4=m_3+1),即证明
(-2s_{m_2}+(2m_2-i-j+1)a_{m_2}geq -2s_{m_3}+(2m_3-i-j+1)a_{m_3})
(-2a_{m_2}+(2m_2-i-j+1)a_{m_2}geq (2m_3-i-j+1)a_{m_3})
((2m_2-i-j-1)a_{m_2}geq (2m_3-i-j+1)a_{m_3})
((2(m_3+1)-i-j-1)a_{m_2}geq (2m_3-i-j+1)a_{m_3})
((2m_3-i-j+1)a_{m_2}geq (2m_3-i-j+1)a_{m_3})
(a_{m_2}geq a_{m_3})
因为坐标递增,显然(a_{m_2}geq a_{m_3}),于是得证
II. $i+jin $偶
同理
(m_1=m_2=m_3=m_4-1),即证
(-2s_{m_2}+(2m_2-i-j-1)a_{m_2}geq-2s_{m_4}+(2m_4-i-j-1)a_{m_4})
((2m_2-i-j-1)a_{m_2}geq-2a_{m_4}+(2m_4-i-j-1)a_{m_4})
((2m_2-i-j-1)a_{m_2}geq(2(m_2+1)-i-j-3)a_{m_4})
((2m_2-i-j-1)a_{m_2}geq(2m_2-i-j-1)a_{m_4})
((i+j-i-j-1)a_{m_2}geq(i+j-i-j-1)a_{m_4})
(-a_{m_2}geq -a_{m_4})
(a_{m_2}leq a_{m_4})
显然成立
总上所素,根据四边形不等式判定定理,易知w满足四边形不等式
因此枚举i的话,根据一维线性递推决策递增定理,易知决策点单调递增,我们只要用单调队列维护三元组即可,时间复杂度(O(pvlog_2^v))
参考代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#define il inline
#define ri register
using namespace std;
struct san{
int l,r,p;
}T[350];
int a[350],s[350],mid[350][350],
dp[35][350],L,R;
il int dfs(int,int,int);
template<class free>
il free Min(free a,free b);
int main(){
int v,p;
scanf("%d%d",&v,&p);
for(int i(1);i<=v;++i)
scanf("%d",&a[i]),s[i]=s[i-1]+a[i];
for(int i(1),j,m;i<=v;++i)
for(j=i+1;j<=v;++j)
m=(i+j)/2,mid[i][j]=s[j]
-s[m]-(s[m]-s[i-1])+a[m]*(2*m-i+1-j);
memset(dp,1,sizeof(dp)),dp[0][0]=0;
for(int i(1),j,k;i<=p;++i){
L=R=1,T[1].l=1,T[1].r=v,T[1].p=0;
for(dp[i][0]=0,j=1;j<=v;++j){
dp[i][j]=dp[i-1][T[L].p]+mid[T[L].p+1][j];if(++T[L].l>T[L].r)++L;
while(L<=R&&dp[i-1][T[R].p]+mid[T[R].p+1][T[R].l]>=
dp[i-1][j]+mid[j+1][T[R].l])--R;T[++R].r=v,T[R].p=j;
if(L<R){T[R].l=dfs(R-1,j,i-1),T[R-1].r=T[R].l-1;if(T[R].l>T[R].r)--R;}
}
}
printf("%d",dp[p][v]);
return 0;
}
il int dfs(int t,int p,int i){
int l(T[t].l),mid,r(T[t].r);
while(l<=r){
mid=l+r>>1;
if(dp[i][T[t].p]+::mid[T[t].p+1][mid]<
dp[i][p]+::mid[p+1][mid])l=mid+1;
else r=mid-1;
}return l;
}
template<class free>
il free Min(free a,free b){
return a<b?a:b;
}
step3
注意到i,j可以看成一段区间,因为(ileq j),于是由于w满足四边形不等式,边界(f[i][i]=w[i][i]=0),而且w满足包含递增
求证:w满足包含递增
证明:
假设增加一个坐标可以让比不增加一个坐标结果优秀,那么直接用增加一个坐标的方案替换不增加一个坐标的方案,会让结果更优秀,矛盾,得证。
于是根据二维递推判定定理,易知f满足四边形不等式,然后又二维递推决策递增定理,容易知道(设(P[l][r])为(f[l][r])的最优决策点),(P[l][r-1]leq P[l][r]leq P[l+1][r]),于是我们可以优化成(O(n^2))。
参考代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#define il inline
#define ri register
using namespace std;
int x[550],s[550],mid[550][550],
dp[550][550],P[550][550];
template<class free>
il free Min(free,free);
int main(){
int v,p;scanf("%d%d",&v,&p);
memset(dp,1,sizeof(dp));
for(int i(1),j,m;i<=v;++i){
scanf("%d",&x[i]),s[i]=s[i-1]+x[i];
for(j=i-1;j;--j)
m=i+j>>1,mid[j][i]=s[i]-
s[m]*2+s[j-1]+(2*m-j-i+1)*x[m];
P[i][i]=i-1,dp[i][i]=0;
}dp[0][0]=0;
for(int i,j(1),k;j<=v;++j)
for(i=j-1;i;--i)
for(k=P[i][j-1];k<=P[i+1][j];++k)
if(dp[i][j]>dp[i-1][k]+mid[k+1][j])
dp[i][j]=dp[i-1][k]+mid[k+1][j],P[i][j]=k;
printf("%d",dp[p][v]);
return 0;
}
template<class free>
il free Min(free a,free b){
return a<b?a:b;
}