[周末训练]征途
题目
【内存限制:$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
【数据范围与提示】
题解
【做题经历】
找到了本题关键之后,打了个裸的$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; }