zoukankan      html  css  js  c++  java
  • 洛谷P4072 [SDOI2016]征途(带权二分,斜率优化)

    洛谷题目传送门

    一开始肯定要把题目要求的式子给写出来

    我们知道方差的公式(s^2=frac{sumlimits_{i=1}^{m}(x_i-overline x)^2}{m})

    题目要乘(m^2)再输出,于是

    (m^2s^2=msumlimits_{i=1}^{m}(x_i-overline x)^2)

    (=m(sumlimits_{i=1}^{m}x_i^2-2overline{x}sumlimits_{i=1}^{m}x_i+moverline{x}^2))

    (=msumlimits_{i=1}^{m}x_i^2-(sumlimits_{i=1}^{m}x_i)^2)

    于是只要最小化(sumlimits_{i=1}^{m}x_i^2)即可。

    然而选(m)段非常不好办。这时候可以联想到凸优化。设(G_m)表示选(m)(sumlimits_{i=1}^{m}x_i^2)的最小值,当(m)增大的时候(G_m)显然会减小,凭蒟蒻的感性理解,多分出一段对答案的影响幅度也越来越小,也就是说(G_x)关于(x)的函数图像大概是下凸的。

    我们用一个斜率为(mid)的直线去切这个凸包。显然(mid)的下界取([0,1])之间的斜率,是总路程平方级别的,上界是(0)。因为切线在凸包的下方,所以多选一段的代价不是(+mid)而是(-mid)

    update:蒟蒻弃用了用直线切凸包的理解方法,蒟蒻用导数思想理解DP凸优化的思路可以看这里

    接下来就是斜率优化的过程。设(f_i)为前(i)条路的最优答案,(x_i)为路程长度的前缀和,写出转移方程

    (f_i=minlimits_{j=0}^{i}{f_j-2x_ix_j+x_j^2}+x_i^2)

    决策(j)优于(k)当且仅当

    (f_j-2x_ix_j+x_j^2<f_k-2x_ix_k+x_k^2)

    (frac{f_j+x_j^2-f_k-x_k^2}{x_j-x_k}<2x_i)

    于是设(y_i=f_i+x_i^2),把决策看成点((x_i,y_i)),使用单调队列就OK了。注意这里要记(c_i)表示最优决策下将前(i)条路分出的段数。最后判断(c_n)(m)的关系来调整斜率。

    由于这一题的斜率肯定不会有小数,故也不必担心二分中的一些边界问题。

    #include<cstdio>
    #define RG register
    #define R RG int
    #define G c=getchar()
    #define Calc(j,k) (y[j]-y[k])/(x[j]-x[k])
    typedef long long LL;
    const int N=3009;
    int n,q[N],c[N];
    double f[N],k[N],x[N],y[N];
    inline int in(){
    	RG char G;
    	while(c<'-')G;
    	R x=c&15;G;
    	while(c>'-')x=x*10+(c&15),G;
    	return x;
    }
    inline double sqr(RG double x){
    	return x*x;
    }
    inline void work(R mid){//斜率优化
    	R h,t,i;
    	for(h=t=i=1;i<=n;++i){
    		while(h<t&&k[h]<2*x[i])++h;
    		f[i]=f[q[h]]+sqr(x[i]-x[q[h]])-mid;//每转移一次要减一下mid
    		y[i]=f[i]+sqr(x[i]);
    		c[i]=c[q[h]]+1;//记录段数
    		while(h<t&&k[t-1]>Calc(q[t],i))--t;
    		k[t]=Calc(q[t],i);q[++t]=i;
    	}
    }
    int main(){
    	n=in();R m=in(),l,r,mid,i;
    	for(i=1;i<=n;++i)x[i]=x[i-1]+in();
    	l=-sqr(x[n]);r=0;//大致确定下界
    	while(l<r){
    		work(mid=(l+r+1)/2);//注意负数的下取整问题
    		c[n]<=m?l=mid:r=mid-1;
    	}
    	work(l);
    	printf("%.0lf
    ",m*(f[n]+m*l)-sqr(x[n]));//先加回m*l
    	return 0;
    }
    
  • 相关阅读:
    PythonのTkinter基本原理
    使用 Word (VBA) 分割长图到多页
    如何使用 Shebang Line (Python 虚拟环境)
    将常用的 VBScript 脚本放到任务栏 (Pin VBScript to Taskbar)
    关于 VBScript 中的 CreateObject
    Windows Scripting Host (WSH) 是什么?
    Component Object Model (COM) 是什么?
    IOS 打开中文 html 文件,显示乱码的问题
    科技发展时间线(Technology Timeline)
    列置换密码
  • 原文地址:https://www.cnblogs.com/flashhu/p/9545380.html
Copyright © 2011-2022 走看看