zoukankan      html  css  js  c++  java
  • 【BZOJ4518】[SDOI2016] 征途(重拾斜率优化DP)

    点此看题面

    大致题意: 让你把一个长度为(n)的序列划分成(m)块,求每块数总和的最小方差乘(m^2)的值。

    转化方差

    首先方差显然是一个比较复杂的东西,需要进行一定转化。

    (p_i)为第(i)块数总和;(s_i)为原序列的前缀和,即(s_i=sum_{i=1}^ia_i)(ar p)(p_i)的平均值,即(ar{p}=frac{sum_{i=1}^mp_i}m=frac{s_n}m)

    然后推式子:

    [m^2*frac{sum_{i=1}^m(p_i-ar{p})^2}m=msum_{i=1}^m(p_i^2-2p_iar{p}+ar{p}^2)=msum_{i=1}^mp_i^2-2mar{p}sum_{i=1}^mp_i+m^2ar{p}^2 ]

    其中(sum_{i=1}^mp_i)显然就是(s_n),同时我们把(ar{p})的值代入得到:

    [msum_{i=1}^mp_i^2-2s_n^2+s_n^2=msum_{i=1}^mp_i^2-s_n^2 ]

    动态规划

    考虑上面这个式子,其中(m,s_n^2)都是常数,因此我们只需要最小化(p_i)平方和。

    可以考虑动态规划

    (f_{i,j})表示当前第(i)位,已划分出(j)块时的(p_i)平方和的最小值。

    显然暴力转移只需枚举一个转移点:

    [f_{i,j}=min_{x=1}^{i-1}(f_{x,j-1}+(s_i-s_x)^2) ]

    这就是(O(n^3))的做法了。

    斜率优化

    显然上面的(DP)还不够优,需要优化。

    这里我们考虑斜率优化。

    设当前为(i),比较对于(a)(b)两个转移点,若我们选择(a)进行转移,则需要满足:

    [f_{a,j-1}+(s_i-s_a)^2<f_{b,j-1}+(s_i-s_b)^2 ]

    拆平方并移项:

    [2s_i(s_b-s_a)<(f_{b,j-1}+s_b^2)-(f_{a,j-1}+s_a^2) ]

    两边同除以(s_b-s_a)得:

    [2s_i<frac{(f_{b,j-1}+s_b^2)-(f_{a,j-1}+s_a^2)}{s_b-s_a} ]

    (A(x)=s_x,B(x)=f_{x,j-1}+s_x^2),则上面的式子就相当于:

    [2s_i<frac{B(b)-B(a)}{A(b)-A(a)} ]

    这是一个斜率的形式。

    那么我们就可以开一个单调队列维护一个斜率逐渐上升的序列。

    然后每次转移之前,将队首斜率小于(2s_i)的几项弹掉再转移即可。

    代码

    #include<bits/stdc++.h>
    #define Tp template<typename Ty>
    #define Ts template<typename Ty,typename... Ar>
    #define Reg register
    #define RI Reg int
    #define Con const
    #define CI Con int&
    #define I inline
    #define W while
    #define N 3000
    #define INF 1e9
    using namespace std;
    int n,m,a[N+5],s[N+5],q[N+5],f[N+5][N+5];
    int main()
    {
    	RI i,j,H,T;for(scanf("%d%d",&n,&m),i=1;i<=n;++i) scanf("%d",a+i),s[i]=s[i-1]+a[i];//读入+初始化前缀和
    	#define A(x) (s[x])
    	#define B(x) (f[x][j-1]+s[x]*s[x])
    	#define S(x,y) (1.0*(B(y)-B(x))/(A(y)-A(x)))
    	#define Slope (2.0*s[i])
    	for(i=1;i<=n;++i) f[i][0]=INF;//初始化
    	for(j=1;j<=m;++j) for(q[H=T=1]=0,i=1;i<=n;++i)//注意要先枚j
    	{
    		W(H<T&&Slope>=S(q[H],q[H+1])) ++H;//弹掉不合法队首
    		f[i][j]=f[q[H]][j-1]+(s[i]-s[q[H]])*(s[i]-s[q[H]]);//转移
    		W(H<T&&S(q[T-1],q[T])>=S(q[T-1],i)) --T;q[++T]=i;//保证单调递增,放入队尾
    	}return printf("%lld",1LL*m*f[n][m]-1LL*s[n]*s[n]),0;//输出答案
    }
    
  • 相关阅读:
    C语言单链表创建,插入,删除
    Java乔晓松spring构造函数的注入以及null的注入
    sentilib_语料库项目_search模块的实现
    spring入门(6)set方法注入依赖之null的注入
    Java乔晓松使用Filter过滤器清除网页缓存
    漂亮的弹框
    C#判断各种字符串(如手机号)
    视频数字水印
    数据校验
    SVN 常见问题操作总结
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/BZOJ4518.html
Copyright © 2011-2022 走看看