bzoj4518: [Sdoi2016]征途

题目链接

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;
}
posted @ 2018-04-03 22:03  zzzzx  阅读(208)  评论(1编辑  收藏  举报