bzoj4518: [Sdoi2016]征途
题目链接
题解
用\(a_i\)表示没天走的路程,\(s_i\)表示\(a_i\)的前缀和
得到式子
\(ans=m^2*{1\over m}[\sum\limits_{i=1}^m (a_i-{s_n\over m})^2]\)
展开化简得到
\(=m(\sum\limits_{i=1}^ma_i^2+{s_n^2\over m}-{2s_n\sum\limits_{i=1}^ma_i\over m})\)
\(=m\sum\limits_{i=1}^m{a_i}^2-{s_n}^2\)
发现式子至于\(a_i ^{2}\)有关
就是将n个数划分成m个部分然后让这些部分的平方和最小
令\(dp(i,j)\)表示前i个数划分了j次的最小花费
那么枚举k \(dp(i,j)=min\{{dp(i-1,k)+{(s_j-s_k)}^2}\}\)
把上面的式子展开得到
\(dp(i,j)=min\{-2s_ks_j+dp(i-1,k)+s_k^2+s_j^2\}\)
然后就可以斜率优化惹
然后你可以滚掉一维 没用
代码
// luogu-judger-enable-o2
#include<cstdio>
#include<cstring>
#include<algorithm>
inline int read() {
int x = 0,f = 1;
char c = getchar ();
while(c < '0' || c > '9') { if(c == '-') f = -1;c = getchar();}
while(c <= '9' && c >= '0') x = x * 10 + c - '0' ,c = getchar();
return x * f;
}
const int maxn = 3007;
int n,m;
int a[maxn];
int dp[maxn][maxn];
int q[maxn];
double calc(int i,int j,int k) {
return ((1.0 * dp[i][j] + 1.0 * a[j] * a[j]) - (1.0 * dp[i][k] + 1.0 * a[k] * a[k])) / (1.0 * a[j] - a[k]);
}
int main() {
//freopen("journey.in","r",stdin);freopen("journey.out","w",stdout);
int n = read(),m = read();
for(int i=1;i<=n;i++)
a[i] += a[i-1] + read();
for(int i = 1;i <= n;++ i) dp[1][i] = a[i] * a[i];
for(int head,tail,i = 2;i <= m;i ++) {
head = 1, tail = 0;
for(int j = 1;j <= n;j ++) {
while(head < tail && calc(i - 1,q[head],q[head + 1 ]) < 2.0 * a[j]) head ++;
dp[i][j] = dp[i - 1][q[head]] + (a[j] - a[q[head]]) * (a[j]-a[q[head]]);
while(head < tail && calc(i - 1,q[tail],q[tail - 1]) > calc(i-1,q[tail],j) )tail --;
q[++tail]=j;
}
}
printf("%d\n",m * dp[m][n] - a[n] * a[n]);
return 0;
}