• 「kuangbin带你飞」专题二十 斜率DP



    layout: post
    title: 「kuangbin带你飞」专题二十 斜率DP
    author: "luowentaoaa"
    catalog: true
    tags:
    mathjax: true
    - kuangbin
    - 动态规划
    - 斜率DP


    传送门

    A.HDU - 3507 Print Article

    题意

    就是输出序列a[n],每连续输出的费用是连续输出的数字和的平方加上常数M

    让我们求这个费用的最小值。

    题解

    概率DP的入门题,把我搞得要死要活的。

    首先dp[i]表示输出前i个的最小费用 很简单得得出一个方程

    [dp[i]=min(dp[i],dp[j]+(sum[i]-sum[j])^2\1<=j<i ]

    其中sum[i]表示数字的前i项和,但是这个方程的复杂度是n^2 所以这时候就要用到斜率优化 ,ps:个人感觉斜率DP都用到了队列,来把前面绝对不优秀的项都出队,这样每次运算都只要在队列中找就行,而且每个元素只有一次出队和入队 所以复杂度只有N

    首先假设在算dp[i]的 ,k<j<i,并且J点比K点优秀

    那么

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

    对上面方程分解整理得:

    [[(dp[j]+sum[j]^2)-(dp[k]+sum[k]^2)]÷2(sum[j]-sum[k])<=sum[i] ]

    注意正负号,不然会影响不等号的方向

    [Y(x)=dp[x]+sum[j]^2\X(x)=2×sum[j] ]

    于是上面的式子变成斜率表达式

    [[Y(j)-Y(k)]/[X(j)-X(k)]<=sum[i] ]

    由于不等式右边的sum[i]随着i的增加而递增

    所以我们另

    [g[j,k]=[Y(j)-Y(k)]/[X(j)-X(k)] ]

    1.如果上面的不等式成立 说明J比K优,而且随着i的增加上述不等式一定是成立的,也就是对于以后的i来说J都比K优秀,所以K是可以淘汰的

    2.如果

    [g[J,K]>g[I,J]\k<j<i ]

    那么J是可以淘汰的

    假设g[I,J]<sum[i] 就是I比J优秀,那么J就没存在的价值

    相反,如果g[I,J]>sum[i] 那么同样有g[J,K]>sum[I] 那么K比J优秀 所以J是可以淘汰的

    所以这样相当于维护一个下凸的图形,斜率在增加,用队列维护

    ps:以上都是抄bin巨的博客

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    #define pp pair<int,int>
    const ll mod=998244353;
    const int maxn=5e5+50;
    const ll inf=0x3f3f3f3f3f3f3f3fLL;
    int gcd(int a,int b){while(b){int t=a%b;a=b;b=t;}return a;}
    int lcm(int a,int b){return a*b/gcd(a,b);}
    int dp[maxn];
    int sum[maxn];
    int a[maxn];
    int q[maxn];
    int m,n;
    int head,tail;
    int getDP(int i,int j){
        return dp[j]+m+(sum[i]-sum[j])*(sum[i]-sum[j]);
    }
    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 main()
    {
        std::ios::sync_with_stdio(false);
        std::cin.tie(0);
        std::cout.tie(0);
        while(cin>>n>>m){
            for(int i=1;i<=n;i++)cin>>a[i];
            memset(sum,0,sizeof(sum));
            memset(dp,0,sizeof(dp));
            for(int i=1;i<=n;i++)sum[i]=sum[i-1]+a[i];
            head=tail=0;
            q[tail++]=0;
            for(int i=1;i<=n;i++){
                while(head+1<tail&&getUP(q[head+1],q[head])<=sum[i]*getDOWN(q[head+1],q[head]))
                    head++;
                dp[i]=getDP(i,q[head]);
                while(head+1<tail&&getUP(i,q[tail-1])*getDOWN(q[tail-1],q[tail-2])<=getUP(q[tail-1],q[tail-2])*getDOWN(i,q[tail-1]))
                    tail--;
                q[tail++]=i;
            }
            cout<<dp[n]<<endl;
        }
        return 0;
    }
    
  • 相关阅读:
    Linux日志文件utmp、wtmp、lastlog、messages
    Linux日志五大命令详解
    php 函数合并 array_merge 与 + 的区别
    MySQL对数据表进行分组查询(GROUP BY)
    如何在mysql中查询每个分组的前几名
    Autojump:一个可以在 Linux 文件系统快速导航的高级 cd 命令
    linux 查看磁盘空间大小
    js刷新页面方法大全
    [知乎有感] 读研到底为了什么,值不值得?
    [Hadoop] 在Ubuntu系统上一步步搭建Hadoop(单机模式)
  • 原文地址:https://www.cnblogs.com/luowentao/p/10332302.html
Copyright © 2020-2023  润新知