• 斜率优化


    斜率优化

    hdu3507
    要输出N个数字a[N],输出的时候可以连续的输出,每连续输出一串,它的费用是 :这串数字和的平方加上一个常数M。求最小的;
    0 ≤ n ≤ 500000, 0 ≤ M ≤ 1000

    首先最暴力的:dp[i]表示输出到i的最小费用是多少;(dp[i]=min{dp[j]+(s[i]-s[j])^2+M})其中s[i]表示1~i的前缀和;即sum

    然后我们引入并且考虑斜率优化:

    设k<j,并且j优于k;

    (dp[j]+(s[i]-s[j])^2+M<dp[k]+(s[i]-s[k])^2+M)

    (dp[j]+s[j]^2-2s[i]s[j]<dp[k]+s[k]^2-2s[i]s[k])//约掉都有的

    ((dp[j]+s[j]^2)-(dp[k]+s[k]^2)<2s[i]*(s[j]-s[k]))

    (frac{(dp[j]+s[j]^2)-(dp[k]+s[k]^2)}{(s[j]-s[k])}<2s[i]) ·········①

    ((s[j],dp[j]+s[j]^2))看做点;(从这里设(s[j]=x[j],dp[j]+s[j]^2=y[j]))

    那么①左边就是点j和点k的斜率吖;

    那么这道题,对于状态i,我们就可以找平面上(1~i-1)的一坨点,判断两者连线的斜率<2s[i],如果斜率<2s[i],说明被减数更优,否则减数更优;

    然后显然两两枚举也不够优,我们考虑三个点jkl,

    由图片可以看出kj的斜率>lk的斜率:

    (frac{y[k]-y[j]}{x[k]-x[j]}>frac{y[l]-y[k]}{x[l]-x[k]})

    关于kj的斜率,lk的斜率与2s[i]比较有三种可能:

    (2s[i]>frac{y[k]-y[j]}{x[k]-x[j]}>frac{y[l]-y[k]}{x[l]-x[k]})

    此时k比j优,l比k优,因此l最优;

    (frac{y[k]-y[j]}{x[k]-x[j]}>2s[i]>frac{y[l]-y[k]}{x[l]-x[k]})

    此时j比k优,l比k优,无法确定谁最优;

    (frac{y[k]-y[j]}{x[k]-x[j]}>frac{y[l]-y[k]}{x[l]-x[k]}>2s[i])

    此时j比k优,k比l优,因此j最优;

    但不管怎样,我们可以发现,k不会是最优的!

    k没用!(雾

    然后我们可以推知:

    当前所有点中,只有在最下方的点才会对最优值产生影响:

    那么对于i点的最优值就是某一点的斜率最接近2s[i]并且小于2s[i]的点(二分来做nlogn);

    继续想新加入一个点c,

    那么可以看到,ab点的斜率要比ac点的大,那么我们就可以将a点删去,同理b也可以删去,但是d要保留;

    同时,因为斜率是递增的,所以如果某个点在这个状态所对应的点之前,它一定不可能再产生贡献,也可以直接删除掉;

    这样每次删除队首保证答案就是队首元素,然后加入的点向队尾添加同时判断弹出一部分队尾元素,这样每个点只进队一次,复杂度就是O(n)的。

    int getup(int j,int k){
    	return dp[j]+sum[j]*sum[j]-dp[k]-sum[k]*sum[k];
    }
    
    int getdown(int j,int k){
    	return 2*(sum[j]-sum[k]); 
    }
    
    int getdp(int i,int j){
    	return dp[j]+(sum[i]-sum[j])*(sum[i]-sum[j])+m;	
    }
    
    head=tail=1;
    q[1]=0;
    for(int i=1;i<=n;i++){
    	while(head<tail&&getup(q[head+1],q[head])//将队首不到正确答案的点弹出 
    	<=sum[i]*getdown(q[head+1],q[head])) head++;//用乘法代替除法来保证精度
    	//意思是保证①式要<=s[i] (这里的2也放到左边去了 
    	
    	dp[i]=getdp(i,q[head]);//将所有不和条件的都弹出了以后,队首就是答案 
    	
    	while(getup(i,q[tail])*getdown(q[tail],q[tail-1])//把除法转化成乘法; 
    	<=getup(q[tail],q[tail-1])*getdown(i,q[tail]))
    	tail--; 
    	/*转化之前: getup(i,q[tail])/getdown(i,q[tail])计算新加入的结点和队尾结点的斜率 
    	<=getup(q[tail],q[tail-1])/getdown(q[tail],q[tail-1]) 计算队尾结点的前一结点和队尾结点的斜率
    	判断是否合法,如果不合法,我们将其删去; 
    	*/ 
    	q[++tail]=i;//把新节点加进去 
    }
    
    

    一模一样的练习题: bzoj3156

    首先来看转移方程

    定义f[i]表示在i建塔并计算完了i之前的最小花费。

    未化简的方程:(f[i]=min{f[j]+a[i]+sumlimits_{k=j+1}^{i-1}(i-k)})

    解释一下就是枚举一个j表示i上一个塔建在了哪里,这样j之前的值可以直接加上,然后+建第i个塔所需花费a[i],对于i和j之间的所有点,都需要放木偶,每个木偶的花费就是i-k(花费是这个点右边建的第一个守卫塔i到这个点的距离)也就是(sum)

    然后化简:(f[i]=min{f[j]+a[i]+(i-j-1)*(i-j)/2})

    考虑:i和j之间一共有i-j-1个点,我们可以找到每两个点,他们的价值之和是i-j,所以答案就是点数*距离/2;

    设k>j,且k优于j。
    f[k]+(i-k)*(i-k-1)/2+a[i]<f[j]+(i-j)*(i-j-1)/2+a[i]
    设K=k+1,J=j+1 为了好推

    最后一步手写一下,把K,J带回去:

    (frac{2*(f[k]-f[j])+k*(k+1)-j*(j+1)}{k+k+1-j-j-1}=frac{2*(f[k]-f[j])+k*k+k-j*j-j}{2*(k-j)})

    然后是代码.jpg:

  • 相关阅读:
    用ildasm和ilasm对.net下的exe程序进行破解初探
    随笔--各种概念等等
    C#高级编程学习一-----------------第五章泛型
    visual studio相关操作
    webservice开发
    delphi各种错
    android开发文章收藏
    android开发遇到的问题
    android应用开发基础知道
    数据库各种问题
  • 原文地址:https://www.cnblogs.com/zhuier-xquan/p/11326711.html
Copyright © 2020-2023  润新知