http://acm.hdu.edu.cn/showproblem.php?pid=2829
题意:将长度为n的序列分成p+1块,使得$sum_{每块}sum_{i<j} a[i]a[j]$最小(n<=1000,1<=a[i]<=100)
#include <cstdio> #include <cstring> #include <algorithm> #include <cmath> #include <iostream> using namespace std; const int N=10005; typedef long long ll; ll d[2][N], s1[N], s2[N]; int n, p, x[N], s[2][N]; inline ll sqr(ll a) { return a*a; } inline ll w(int l, int r) { return (sqr(s1[r]-s1[l-1])-(s2[r]-s2[l-1]))>>1; } int main() { while(scanf("%d%d", &n, &p), !(n==0&&p==0)) { ++p; for(int i=1; i<=n; ++i) scanf("%d", &x[i]), s1[i]=s1[i-1]+x[i], s2[i]=s2[i-1]+sqr(x[i]); int h=0, t=1; for(int i=2; i<=n; ++i) d[h][i]=w(1, i), s[h][i]=1; for(int i=2; i<=p; ++i) { s[t][n+1]=n; for(int j=n; j>=1; --j) { int l=s[h][j], r=s[t][j+1], &pos=s[t][j]; ll &now=d[t][j]; now=~0ull>>1; for(int k=l; k<=r; ++k) { ll t=d[h][k-1]+w(k, j); if(now>=t) now=t, pos=k; } } swap(t, h); } printf("%lld ", d[h][n]); } return 0; }
设$d(i, j)$表示分$i$份前$j$个的最小代价,容易得到
$$d(i, j) = min { d(i-1, k-1) + w(k, j) }, i<j $$
其中
$$w(i, j) = frac{sum1(i, j)^2-sum2(i, j)}{-2}$$
其中$sum1(i, j)=sum_{i}^{j} a[i], sum2(i, j)=sum_{i}^{j} a[i]^2$
(可以由$(sum_{i} a[i] )^2 = sum_{i} a[i]^2 - 2 sum_{i<j} a[i]a[j]$得到= =,然后$O(n)$求出来,虽然你们都是用$O(n^2)$预处理的= =(我有常数强迫症= =))
证明挺好证的(这里只证明$w$的四边形不等式,剩下证明与前面两题相同= =我就不证明了= =
证明四边形不等式即证明当$j$固定时,$w(i, j+1)-w(i, j)$是关于$i$的递减函数
$$
egin{align}
& w(i, j+1)-w(i, j)
otag \
= & frac{sum1(i, j+1)^2-sum2(i, j+1)}{-2} - frac{sum1(i, j)^2-sum2(i, j)}{-2} \
= & frac{sum1(i, j+1)^2 - sum1(i, j)^2 + sum2(i, j) - sum2(i, j+1)}{-2} \
= & frac{a[j+1]^2 - 2a[j+1]sum_{k=i}^{j} a[k] - a[j+1]^2}{-2} \
= & a[j+1]sum_{k=i}^{j} a[k]
end{align}
$$
当j不变时i递增,显然式子减小,得证。