[Luogu P4072 [SDOI2016]征途]题解

题面传送

Pine希望每一天走的路长度尽可能相近,所以他希望每一天走的路的长度的方差尽可能小。
提到了方差???
那么自然而然我们可以想到方差的公式[推导公式会按步骤给出]

\[s^2=\frac{\sum_{i=1}^{m}(v_i-\overline{v})^2}{m} \]

\[\Rightarrow~~~~~~~~~~~~s^2=\frac{\sum_{i=1}^{m}~(v_i^2-2v_i\overline{v}+\overline{v}^2)}{m} \]

\[\Rightarrow~~~~~ms^2=\sum_{i=1}^{m}v_i^2-2\overline{v}\sum_{i=1}^{m}v_i+\sum_{i=1}^{m}\overline{v}^2 \]

\[\because~~~~~\overline{v}=\frac{\sum_{i=1}^{m}v_i}{m} \]

\[\therefore~~~~~ms^2=\sum_{i=1}^{m}v_i^2 - 2\frac{\sum_{i=1}^{m}v_i}{m}\sum_{i=1}^{m}v_i + \sum_{i=1}^{m}(\frac{\sum_{i=1}^{m}v_i}{m})^2 \]

\[又\because~~~~~\frac{\sum_{i=1}^{m}x}{m}=x \]

\[\therefore~~~~~ms^2=\sum_{i=1}^{m}v_i^2 - 2\frac{(\sum_{i=1}^{m}v_i)^2}{m} + \frac{(\sum_{i=1}^{m}v_i)^2}{m} \]

\[\therefore~~~~~m^2s^2=m\sum_{i=1}^{m}v_i^2 - (\sum_{i=1}^{m}v_i)^2 \]

我们不难发现,我们可以利用前缀和来计算出等式右边的结果

for(register int i=1;i<=n;++i){
    read(s[i]);
    s[i]+=s[i-1];
}
s[i]表示的是等式右边第二项

\[f_{x,y}表示走了前x段一共花了y天, 最小的\sum_{i=1}^{x}v_i^2,转移方程很容易得出为: \]

\[f_{x,y}=\min{( f_{x,y} , f_{k,y-1}+(s[x]-s[k])^2 ) } \]

\[根据m^2s^2=m\sum_{i=1}^{m}v_i^2 - (\sum_{i=1}^{m}v_i)^2 \]

\[最后面的结果m^2s_n^2自然就是m*f_{n,m}-s_n*s_n \]

即可写出代码:

#include<bits/stdc++.h>
using namespace std;
#define N 3005
int f[N][N],s[N];
int n,m;
inline int read(){
	int w=0,sum=0;
	char ch=getchar();
	while(!isdigit(ch)){
		w|=(ch=='-');
		ch=getchar();
	}
	while(isdigit(ch)){
		sum=(sum<<1)+(sum<<3)+(ch^48);
		ch=getchar();	
	}
	return w?-sum:sum;
} 
signed main(){
    memset(f,0x3f,sizeof(f));f[0][0]=0;    
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;++i){
        scanf("%d",&s[i]);
        s[i]+=s[i-1];
    }
    for(int j=1;j<=m;++j)
        for(int i=1;i<=n;++i)
            for(int k=0;k<i;++k)
                f[i][j]=min(f[k][j-1]+(s[i]-s[k])*(s[i]-s[k]),f[i][j]);
    printf("%d\n",m*f[n][m]-s[n]*s[n]);
    return 0;
}

写到这一步「在luogu上」已经有90分的高分了,不过还是AC不了这道题,那么我们如何进一步优化呢?
需要用到斜率优化,在本篇博客中只大概提到一点,若想了解更多或没有前置知识请移步一位dalao的blog
怎么优化呢?有两种方法[可谓是殊途同归]

第一种

回到状态:

\[f_{x,y}=\min{( f_{x,y} , f_{k,y-1}+(s[x]-s[k])^2 ) } \]

我们可以化成

\[f_{x,y}=f_{k,y-1}+(s[x]-s[k])^2 \]

\[\Rightarrow~~~~~~f_{x,y}=f_{k,y-1}+s[x]^2-2s[x]*s[k]+s[k]^2 \]

\[\Rightarrow~~~~~~f_{k,y-1}+s[k]^2=2s[x]*s[k]+f_{x,y}-s[x]^2 \]

\[有一次函数y=kx+b \]

\[我们可以将转移式中的值看成函数中的量 \]

\[ \left\{ \begin{aligned} f_{k,y-1}+s[k]^2 \Rightarrow y \\ 2s[x] \Rightarrow k \\ s[k] \Rightarrow x \\ f_{x,y}-s[x]^2 \Rightarrow b \\ \end{aligned} \right. \]

\[根据斜率计算式~~~~~k=\frac{y_1-y_2}{x_1-x_2}可得 \]

\[那么若j比k更优,则\frac{y_j-y_k}{x_j-x_k}<2s_x​ \]

\[也就是\frac{f_{j,y-1}+s[j]^2-f_{k,y-1}-s[k]^2}{s[j]-s[k]}<2s_x \]




第二种

又一次回到状态:

\[f_{x,y}=\min{( f_{x,y} , f_{k,y-1}+(s[x]-s[k])^2 ) } \]

若j比k更优使f[x]更新后更小那么

\[f_{j,y-1}+(s[x]-s[j])^2 < f_{k,y-1}+(s[x]-s[k])^2 \]

\[\Leftrightarrow​ \frac{f_{j,y-1}+s[j]^2-f_{k,y-1}-s[k]^2}{s[j]-s[k]}<2s_x \]

\[和第一种的一样 \]

有了上述结论,我们就可以很好的用单调队列来维护一个凸壳了

    while(head<tail&&slope(q[head],q[head+1])<2*s[i]) //我们上述讨论的第一、二种情况就是这个用的
        ++head;
    
    while(head<tail&&slope(q[tail-1],q[tail])>slope(q[tail-1],i))// 如果加入后发现前面的那个点凹进去了就弹出前面那个点
        --tail; 
    q[++tail]=i;  

\[Slope函数 \]

\[按照上面的\frac{f_{j,y-1}+s[j]^2-f_{k,y-1}-s[k]^2}{s[j]-s[k]}就行了 \]




各位看官在状态转移时是不是看着二维数组有些心烦,其实我们可以用滚动数组[或者两个数组优化]

f[i]=g[q[head]]+(s[i]-s[q[head]])*(s[i]-s[q[head]]);

\[g数组就是保存的(\sum_{i=1}^{m}v_i)^2 \]




最后的最后附上完整Code
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define N 5005
int n,m,q[N];
ll s[N];
ll f[N],g[N],head=1,tail=1;
inline double X(int x){
    return 1.0*s[x];
}
inline double Y(int x){
    return 1.0*g[x]+1.0*s[x]*s[x];
}
inline double slope(int i,int j){
    return (Y(i)-Y(j))/(X(i)-X(j));
}
signed main(){
    scanf("%d%d",&n,&m);
    for(register int i=1;i<=n;++i){
        scanf("%d",&s[i]);      
        s[i]+=s[i-1];  
        g[i]=s[i]*s[i];
    }
    for(register int l=1;l<m;++l){
        head=tail=1;
        q[head]=l;
        for(register int i=l+1;i<=n;++i){
            while(head<tail&&slope(q[head],q[head+1])<2*s[i]) 
                ++head;
            f[i]=g[q[head]]+(s[i]-s[q[head]])*(s[i]-s[q[head]]);
            while(head<tail&&slope(q[tail-1],q[tail])>slope(q[tail-1],i))
                --tail;      
            q[++tail]=i;
        }
        for(register int i=1;i<=n;++i)
            g[i]=f[i];
    }
    printf("%lld\n",m*f[n]-s[n]*s[n]);
    return 0;
}
posted @ 2021-02-20 20:45  缥灵  阅读(155)  评论(4)    收藏  举报