• HDU-3507 Print Article (斜率优化)


    题目大意:将n个数分成若干个区间,每个区间的代价为区间和的平方加上一个常数m,求最小代价。

    题目分析:定义状态dp(i)表示前 i 个数已经分好的最小代价,则状态转移方程为

    dp(i)=min(dp(j)+(sum(j)-sum(i))^2)+m   <1>。将这个方程整理一下得到:

    dp(i)=min(-2*sum(i)*sum(j)+dp(j)+sum(j)^2)+sum(i)^2+m   <2>。

    设函数f(i)=-2*sum(i)*sum(j)+dp(j)+sum(j)^2+sum(i)^2+m   <3>,则dp(i)=min(f(i))。

    另k(i)=-2*sum(i),x(j)=sum(j),b(j)=dp(j)+sum(j)^2+m。

    则f(i)=k(i)*x(j)+b(j),

    移项得到b(j)=-k(i)*x(j)+f(i)   <4>。

    枚举到i 时,i 是固定的,所以,-k(i)是常量,但f(i)不是常量。j仍是变量,所以x(j)与b(j)的关系便是一元一次函数中x与y的关系,每一个 j 对应一对儿(x,y),将<4>式简记为y=k*x+f(i),这实际上得到了一个斜率为k的直线族。对于 j 对应的每对儿(x,y),f(i)都有一个取值,要使得f(i)取最小值,只需要将族中的一条直线从负无穷远处往上平移,直到遇到第一个点,这时这个点对应的 j 便是最优决策,并且这个点一定会是凸包上的一个点。

      随着 i 的递增,斜率-k(i)=2*sum(i)随之递增,显然,最优决策也会随之递增。这时候,就可以用单调队列来维护一个下凸壳。

    第一次做这种DP,在网上看了一些大牛的博客:

    http://www.cnblogs.com/ka200812/archive/2012/08/03/2621345.html(这个比较易懂)

    http://blog.sina.com.cn/s/blog_508dd1e60100tvk0.html

    其中,又夹杂了自己的一些理解,也不知道理解的对不对,希望您看到错误之后能不吝赐教!

    代码如下:

    # include<iostream>
    # include<cstdio>
    # include<cstring>
    # include<algorithm>
    using namespace std;
    
    int n,m;
    int a[500005];
    int q[500005];
    int dp[500005];
    int sum[500005];
    
    void read(int &x)
    {
        char ch=' ';
        while(ch<'0'||ch>'9') ch=getchar();
        x=0;
        while(ch>='0'&&ch<='9'){
            x=x*10+ch-'0';
            ch=getchar();
        }
    }
    
    void init()
    {
        sum[0]=0;
        for(int i=1;i<=n;++i){
            read(a[i]);
            sum[i]=a[i]+sum[i-1];
        }
    }
    
    int getSon(int i,int j)
    {
        return dp[j]-dp[i]+(sum[j]+sum[i])*(sum[j]-sum[i]);
    }
    
    int getMother(int i,int j)
    {
        return 2*(sum[j]-sum[i]);
    }
    
    int toDp(int i,int j)
    {
        return dp[i]+m+(sum[i]-sum[j])*(sum[i]-sum[j]);
    }
    
    int solve()
    {
        int head=0,tail=-1;
        q[++tail]=0;
        dp[0]=0;
        for(int i=1;i<=n;++i){
            while(head+1<=tail&&getSon(q[head],q[head+1])<=sum[i]*getMother(q[head],q[head+1]))
                ++head;
            dp[i]=toDp(q[head],i);
            while(head+1<=tail&&getSon(q[tail],i)*getMother(q[tail-1],q[tail])<=getSon(q[tail-1],q[tail])*getMother(q[tail],i))
                --tail;
            q[++tail]=i;
        }
        return dp[n];
    }
    
    int main()
    {
        while(~scanf("%d%d",&n,&m))
        {
            init();
            printf("%d
    ",solve());
        }
        return 0;
    }
    

      

  • 相关阅读:
    PHP数组的升序降序函数
    overflow样式修改插件——jscrollpane
    li标签之间的空隙问题(转)
    纯CSS制作三角(转)
    图片放大插件——elevatezoom
    git常用命令笔记
    求数组子序列和最大值
    Vim快速入门
    C++求两个整数的最大公约数和最小公倍数
    实验楼-4-Linux文件权限
  • 原文地址:https://www.cnblogs.com/20143605--pcx/p/5303349.html
Copyright © 2020-2023  润新知