[周末训练]征途

题目

【内存限制:$256MiB$】【时间限制:$1000ms$】

【标准输入输出】【题目类型:传统】评测方式:文本比较】


 

【题目描述】

$Pine$开始了从$S$地到$T$地的征途。

从$S$地到$T$地的路可以划分成$N$段,相邻两段路的分界点设有休息站。

$Pine$计划用$M$天到达$T$地。除第$M$天外,每一天晚上$Pine$都必须在休息站过夜。所以,一段路必须在同一天中走完。

$Pine$希望每一天走的路长度尽可能相近,所以他希望每一天走的路的长度的方差尽可能小。

帮助$Pine$求出最小方差是多少。

设方差是$v$,可以证明,$v×M^2$是一个整数。为了避免精度误差,输出结果时输出$v×M^2$

【输入格式】

第一行两个数$N,M$

第二行$N$个数,表示$N$段路的长度。

【输出格式】

一个数,最小方差乘以$M^2$后的值。

【样例】

样例输入

5 2
1 2 5 8 6

样例输出

36

【数据范围与提示】

对于$30\%$的数据,$1 \leq N \leq 10$
对于$60\%$的数据,$1 \leq N \leq 100$
对于$100\%$的数据,$1 \leq N \leq 3000$
保证从$S$到$T$的总路程不超过$30000$

题解

【做题经历】

找到了本题关键之后,打了个裸的$DP$,结果卡在初始化上面,后来发现这个裸$DP$可得$80pts$

【正解】

看了看题目,发现题目要我们求的东西似乎没怎么给明,那么我们首先应该手动化简或者说是处理一下题目给出的公式$v*M^2$。

首先,我们设题目中的$N$个数$a_1,a_2......a_N$通过某种方式的组合后变为$M$个数$x_1,x_2......x_M$。

令$S=\sum_{i=1}^{M}x_i$

那么通过方差公式可得

$$v=\frac{\sum_{i=1}^{M}(x_i-\frac{S}{M})}{M}$$

通过平方差公式展开,可得

$$v=\frac{\sum_{i=1}^{M}({x_i}^2+\frac{S^2}{M^2}-2x_i\frac{S}{M})}{M}$$

而我们要求的是$v×M^2$,不要忘记$M^2$,现在将$M^2$乘进去,可得

$$v*M^2=M\cdot \sum_{i=1}^{M}({x_i}^2+\frac{S^2}{M^2}-2x_i\frac{S}{M})$$

将括号打开,可得

$$vM^2=M\sum_{i=1}^{M}{x_i}^2+M^2\frac{S^2}{M^2}-2M\frac{S}{M}\sum_{i=1}^{M}x_i$$

因为$S=\sum_{i=1}^{M}x_i$,所以我们还可以将这个式子化简,可得

$$vM^2=M\sum_{i=1}^{M}{x_i}^2+S^2-2S^2$$

最后,可以得到

$$v×M^2=M\sum_{i=1}^{M}{x_i}^2-S^2$$

在这个式子中,$M$与$S$都是已知的,所以我们只需使求和部分最大即可。

然后开始$DP$:令

$dp[j][i]$:前$i$个数$a_i$组合成$j$个数$x_j$后平方求和$x_j$的最大值。

$s[i]$:前缀和不解释。

那么就有状转$$dp[j][i]=max\{dp[j-1][k]+(s[i]-s[k])^2|k\in [0,i-1]\}$$

然后我们可以码出$80pts$的代码:

#include<bits/stdc++.h>
using namespace std;
#define int long long
template<class T>inline void qread(T& x){
    char c;bool f=false;x=0;
    while((c=getchar())<'0'||'9'<c)if(c=='-')f=true;
    for(x=(c^48);'0'<=(c=getchar())&&c<='9';x=(x<<1)+(x<<3)+(c^48));
    if(f)x=-x; 
}
template<class T,class... Args>inline void qread(T& x,Args&... args){qread(x),qread(args...);}
inline int rqread(){
    char c;bool f=false;int x=0;
    while((c=getchar())<'0'||'9'<c)if(c=='-')f=true;
    for(x=(c^48);'0'<=(c=getchar())&&c<='9';x=(x<<1)+(x<<3)+(c^48));
    return f?-x:x;
}
template<class T>inline T Max(const T x,const T y){return x>y?x:y;}
template<class T>inline T Min(const T x,const T y){return x<y?x:y;}
template<class T>inline T fab(const T x){return x>0?x:-x;}
const int MAXN=3000;
int N,M,a[MAXN+5],s[MAXN+5],dp[MAXN+5][MAXN+5];
signed main(){
    qread(N,M);
    for(int i=1;i<=N;++i)s[i]=s[i-1]+(a[i]=rqread());
    memset(dp,0x3f,sizeof dp);dp[0][0]=0;
    for(int j=1;j<=M;++j)for(int i=1;i<=N;++i)for(int k=0;k<i;++k)
        dp[j][i]=Min(dp[j-1][k]+(s[i]-s[k])*(s[i]-s[k]),dp[j][i]);
    printf("%lld\n",M*dp[M][N]-s[N]*s[N]);
    return 0;
}

这是一个裸的$DP$,可以发现其时间复杂度为$O(NM^2)$,对于最大的$N$为$3000$时,还是会超时的。

考虑为其优化,再看其形式,发现其形如$f(x)=max\{g(x)\}+tmp$,那么这个时候我们就要斜率优化一下。

然后,可以硬扯下一维,最终时间复杂度为$O(NM)$,代码如下:

#include<bits/stdc++.h>
using namespace std;
#define int long long
template<class T>inline void qread(T& x){
    char c;bool f=false;x=0;
    while((c=getchar())<'0'||'9'<c)if(c=='-')f=true;
    for(x=(c^48);'0'<=(c=getchar())&&c<='9';x=(x<<1)+(x<<3)+(c^48));
    if(f)x=-x; 
}
template<class T,class... Args>inline void qread(T& x,Args&... args){qread(x),qread(args...);}
inline int rqread(){
    char c;bool f=false;int x=0;
    while((c=getchar())<'0'||'9'<c)if(c=='-')f=true;
    for(x=(c^48);'0'<=(c=getchar())&&c<='9';x=(x<<1)+(x<<3)+(c^48));
    return f?-x:x;
}
template<class T>inline T Max(const T x,const T y){return x>y?x:y;}
template<class T>inline T Min(const T x,const T y){return x<y?x:y;}
template<class T>inline T fab(const T x){return x>0?x:-x;}
const int MAXN=3000;
const int BUFFER_SIZE=3000;
int N,M,a[MAXN+5],s[MAXN+5],dp[MAXN+5][MAXN+5];
int Q[BUFFER_SIZE+5],head,tail;
inline void init(){head=1,tail=0;}
inline double calc(const int j,const int k,const int l){return (dp[j-1][k]-dp[j-1][l]+s[k]*s[k]-s[l]*s[l])*1.0/(s[k]-s[l]);}
inline int getDp(const int j,const int i,const int k){return dp[j-1][k]+(s[i]-s[k])*(s[i]-s[k]);}
signed main(){
    qread(N,M);
    memset(dp,0x3f,sizeof dp);dp[0][0]=0;
    for(int i=1;i<=N;++i)s[i]=s[i-1]+(a[i]=rqread()),dp[1][i]=s[i]*s[i];
    for(int j=2;j<=M;++j){
        init();
        for(int i=1;i<=N;++i){
            while(head<tail&&calc(j,Q[head],Q[head+1])<2.0*s[i])++head;
            dp[j][i]=getDp(j,i,Q[head]);
            while(head<tail&&calc(j,Q[tail-1],Q[tail])>calc(j,Q[tail],i))--tail;
            Q[++tail]=i;
        }
    }
    printf("%lld\n",M*dp[M][N]-s[N]*s[N]);
    return 0;
}

 

posted @ 2019-10-06 17:06  南枙向暖  阅读(154)  评论(0编辑  收藏  举报