斜率优化dp

斜率优化dp

一种和数学联系很大的\(dp\)的优化方式

做题时的思路一般如下:

如果做\(dp\)题时,这道题转移\(dp[i]\)的值时需要用到前面的决策\(dp[j]\)的值,且出现了i与j的乘积时,考虑斜率优化

做题步骤如下:

一.根据题意,灵活列出一般的不含前缀和优化的\(dp\)方程,然后根据\(dp\)方程的特点,加上前缀和优化(其它的前缀优化的题也可以这么做)

二.将只与\(j\)有关的式子放在等式的左边,将含有\(“i"\)\(“j"\)的乘积的式子放在等式的右边,将只与i有关的式子放在等式的右边,发现这与平面直角坐标系中直线的斜率式\(y = kx + b\)相似

三.然后找\(x,k\)的单调性:

\(1.\)\(x\)不单调,考虑平衡树\(/\)\(CDQ\)分治

$ 2.\(若\)x$单调,考虑单调队列:

    (1)若k单调,维护单调队列的指针

    (2)若k不单调,在单调队列中使用二分查找

四.画出\(y=kx+b\)简图,考虑k的正负,进一步考虑维护上凸包/下凸包

例题

一本通例题三连

例1 任务安排1

题目链接

题意:有\(n\)个排成一个序列且不能改变顺序的任务需要执行,执行第\(i\)个任务需要时间\(T[i]\),在每批任务完成之前,机器需要\(S\)的启动时间。一个任务完成后需等待该批任务所需时间全部完成。每个任务完成的费用等于它的完成时刻乘以一个费用系数\(C[i]\)。规划一个从费用最小的分组方案。

数据范围:\(1<=n<=5000 1<=S<=50 1<=Ti,Ci<=100\)

法一:复杂度\(O(n^3)\)

\(f[i][j]\)表示前i批任务完成前j个任务的最小费用
\(f[i][j]=min(f[i][j],f[i-1][k]+(S*i+sumT[j])*(sumC[j]-sumC[k]));\)

法二:复杂度\(O(n^2)\)

\(f[i]\)表示完成前i个任务的最小费用
\(f[i]=min(f[i],f[j]+(sumC[i]-sumC[j])*sumT[i]+S*(sumC[n]-sumC[j]));\)
我们没有直接求每个任务的完成时刻,而是在一批任务“开始”对后续任务产生影响时,就先把费用累加到结果中。这是一种名为“费用提前计算”的经典思想。

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
int n,s,T[5010],C[5010];
int sumT[5010],sumC[5010];
int dp[5010];
int main()
{
    scanf("%d%d",&n,&s);
    for(int i=1;i<=n;i++)scanf("%d%d",&T[i],&C[i]);
    for(int i=1;i<=n;i++)
    {
        sumT[i]=sumT[i-1]+T[i];
        sumC[i]=sumC[i-1]+C[i];
    }
    memset(dp,0x3f3f3f3f,sizeof(dp));//
    dp[0]=0;//
    for(int i=1;i<=n;i++)
    {
        for(int j=0;j<i;j++)
        {
            dp[i]=min(dp[i],dp[j]+sumT[i]*(sumC[i]-sumC[j])+s*(sumC[n]-sumC[j]));//max->min //sumC[n]-sumC[i-1]->sumC[n]-sumC[j-1] //sumC[n]-sumC[j-1],sumC[i]-sumC[j-1]中的j-1改成j
        }
    }
    printf("%d\n",dp[n]);
    return 0;
}

例2 任务安排2

题目链接

题意:与\(“\)任务安排1\("\)相同

数据范围:\(1<=n<=3\times10^5 \,\,\,1<=S,Ti,Ci<=512\)

我们发现时间复杂度为\(O(n^2)\)的算法在这里已经行不通了,我们考虑将复杂度优化为\(O(n)\)\(O(nlogn)\)

这里我们发现对于上一道题法二的\(dp\)方程中存在\(“sumC[j]\times sumT[i]”\)一项,考虑斜率优化

将式子去掉\(min\),化为\(f[j]=(S+sumT[i])*sumC[j] + f[i] - sumT[i] * sumC[i] - S * sumC[n]\)

$ y = k x + b$

我们维护一个单调队列和下凸包即可。

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
#define maxn 10100
#define ll long long
int n;
ll s;
ll sumT[maxn],sumC[maxn];
ll f[maxn];
int q[maxn];
signed main()
{
    scanf("%d%lld",&n,&s);
    for(int i=1;i<=n;i++)
    {
        ll T,C;scanf("%lld%lld",&T,&C);
        sumT[i]=sumT[i-1]+T;
        sumC[i]=sumC[i-1]+C;
    }
    int head=1,tail=1;
    memset(f,0x3f3f3f3f,sizeof(f));
    f[0]=0;
    q[1]=0;
    for(int i=1;i<=n;i++)
    {
        while(head<tail&&((f[q[head+1]]-f[q[head]])<=(s+sumT[i])*(sumC[q[head+1]]-sumC[q[head]])))head++;
        f[i]=f[q[head]]+(sumC[i]-sumC[q[head]])*sumT[i]+s*(sumC[n]-sumC[q[head]]);
        while(head<tail&&((f[i]-f[q[tail]])*(sumC[q[tail]]-sumC[q[tail-1]])<=(f[q[tail]]-f[q[tail-1]])*(sumC[i]-sumC[q[tail]])))tail--;
        q[++tail]=i;
    }
    printf("%lld\n",f[n]);
    return 0;
}

例3 任务安排3

题目链接

题意:与“任务安排1“相同

数据范围:\(1<=n<=3\times10^5 \,\,\,1<=S,Ci<=512 \,\,\,-512<=Ti<=512\)

我们发现这道题中\(T\)可能为负数,所以\(sumT[i]\)可能为负数,故斜率k不再单调递增,我们不能用前面的指针维护单调队列了,但是仍需用单调队列维护下凸包。我们可以在单调队列中二分查找一个点\(p\)使得\(p\)左边的线段斜率小于\(k\),右边的线段斜率大于\(k\)。我们注意到\(k\)可能为负,但是经过分析发现这并不影响答案。

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
#define maxn 300100
#define ll long long
int n,l,r;
ll s,sumT[maxn],sumC[maxn];
int q[maxn];
ll f[maxn];
int query(ll x,int y)
{
    if(l==r)return q[l];
    int L=l,R=r,ans=0;
    while(L<=R)
    {
        int mid=(L+R)>>1;
        if((f[q[mid]]-f[q[mid-1]])<=x*(sumC[q[mid]]-sumC[q[mid-1]]))
        {
            ans=mid;L=mid+1;
        }
        else R=mid-1;
    }
    return q[ans];
}
int main()
{
    scanf("%d%lld",&n,&s);
    for(int i=1;i<=n;i++)
    {
        ll T,C;scanf("%lld%lld",&T,&C);
        sumT[i]=sumT[i-1]+T;
        sumC[i]=sumC[i-1]+C;
    }
    l=1,r=1;
    q[1]=0;
    memset(f,0x3f3f3f3f,sizeof(f));
    f[0]=0;
    for(int i=1;i<=n;i++)
    {
        int p=query(s+sumT[i],i);
        f[i]=f[p]+(sumC[i]-sumC[p])*sumT[i]+s*(sumC[n]-sumC[p]);
        while(l<r&&((f[i]-f[q[r]])*(sumC[q[r]]-sumC[q[r-1]])<=(f[q[r]]-f[q[r-1]])*(sumC[i]-sumC[q[r]])))r--;
        q[++r]=i;
    }
    printf("%lld\n",f[n]);
    return 0;
}

玩具装箱

题目链接

题意:有\(n\)件玩具,需要按顺序分批压缩,第\(i\)件玩具压缩后长度变为\(C[i]\),如果将第i件到第j件玩具压缩在一起,容器的长度为\(x=j−i+C_i+C_{i+1}+...+C_j\),长度为x的容器的制作费用为\((x-L)^2\),容器长度任意大,求最小花费。

\(1<=n<=10^5\,\,\, 1<=L,C[i]<=10^7\)

写出\(dp\)方程后发现有\(“i"\)\(“j”\)的乘积,考虑斜率优化,时间复杂度:\(O(n)\)

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
#define maxn 50100
#define ll long long
int n,L;
int q[maxn];
ll sum[maxn],dp[maxn];
ll a(int x){return sum[x]+x;}
ll b(int x){return sum[x]+x+L+1;}
ll X(int x){return b(x);}
ll Y(int x){return dp[x]+b(x)*b(x);}
int main()
{
    scanf("%d%d",&n,&L);
    for(int i=1;i<=n;i++)
    {
        int C;scanf("%d",&C);
        sum[i]=sum[i-1]+C;
    }
    int l=1,r=1;
    q[1]=0;
    memset(dp,0x3f3f3f3f,sizeof(dp));
    dp[0]=0;
    for(int i=1;i<=n;i++)
    {
        while(l<r&&((Y(q[l+1])-Y(q[l]))<=2*a(i)*(X(q[l+1])-X(q[l]))))l++;
        dp[i]=dp[q[l]]+(sum[i]-sum[q[l]]+i-q[l]-1-L)*(sum[i]-sum[q[l]]+i-q[l]-1-L);
        while(l<r&&((Y(i)-Y(q[r]))*(X(q[r])-X(q[r-1]))<=(Y(q[r])-Y(q[r-1]))*(X(i)-X(q[r]))))r--;
        q[++r]=i;
    }
    printf("%lld\n",dp[n]);
    return 0;
}

Cats Transport

题目链接

题意:有\(m\)只猫,\(p\)个铲屎官,\(n\)个山丘,山丘\(i\)与山丘\(i-1\)的距离是\(D[i]\)米,铲屎官们住在\(1\)号山丘,第i只猫去山丘\(H[i]\)玩耍\(T[i]\)个单位时间,铲屎官从山丘\(1\)走到山丘\(n\),移动速度为\(1\)米每单位时间,能带上任意数量的猫子。求猫子们最小的等待时间之和。

\(2<=n<=10^5 1<=m<=10^5 1<=P<=100\)

\(A[i]\)表示铲屎官至少在\(A[i]\)时刻出发能接到第i只猫子

显然铲屎官会接到\(A\)值小于铲屎官出发时间的连续几只猫子,这就转化为了类似于“任务安排”的问题

\(f[i][j]\)表示第\(i\)个铲屎官接到前\(j\)只猫子的最小等待时间,注意当\(i\)为1时要特殊处理。然后就是标准的斜率优化啦。

时间复杂度:\(O(PM)\)

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define maxn 100100
#define ll long long
int n,m,p;
int D[maxn],H[maxn],T[maxn];
ll A[maxn];
ll S[maxn];
int q[maxn],cnt=0;
ll f[110][maxn];
ll X(int i) { return i; }
ll Y(int j) { return f[cnt][j] + S[j]; }//
ll mymin(ll x, ll y){ if(x < y) return x; else return y; }
int main()
{
    scanf("%d%d%d", &n, &m, &p);
    for(int i = 2; i <= n; i++)
    {
        scanf("%d", &D[i]);
        D[i] += D[i - 1];
    }
    for(int i = 1; i <= m; i++)scanf("%d%d", &H[i], &T[i]);
    for(int i = 1; i <= m; i++)
    {
        A[i] = T[i] - D[H[i]];
    }
 //   for(int i = 1;i <= n; i++)printf("D[%d] = %d\n", i, D[i]);
 //   for(int i = 1;i <= m; i++)printf("H[%d] = %d T[%d] = %d A[%d] =%d\n", i, H[i], i, T[i], i, A[i]);
    sort(A + 1, A + m + 1);
    for(int i = 1; i <= m; i++)S[i] = S[i - 1] + A[i];
    memset(f, 0x3f, sizeof(f)); f[0][0] = 0;
    for(int i = 1; i <= p; i++)
    {
        int l = 1, r = 1; cnt = i - 1; q[1] = 0;
        for(int j = 1; j <= m; j++)
        {
            while(l < r && (Y(q[l + 1]) - Y(q[l])) <= A[j] * (X(q[l + 1]) - X(q[l])))l++;
            f[i][j] = f[i - 1][q[l]] + A[j] * (j - q[l]) -(S[j] - S[q[l]]);
            while(l < r && (Y(j) - Y(q[r])) * (X(q[r]) - X(q[r-1])) <= (Y(q[r]) - Y(q[r - 1])) * (X(j) - X(q[r])))r--;//
            q[++r] = j;
        }
    }
    printf("%lld\n", f[p][m]);
    return 0;
}

仓库建设

题目链接

题意:有\(n\)个工厂,由高到低分布在一座山上,工厂\(1\)在山顶,工厂\(n\)在山脚,每个工厂的产品需要存在仓库里,第\(i\)个工厂有\(P[i]\)件成品,在第\(i\)个工厂位置建设仓库的费用为\(C[i]\),产品只能往山下运,已知工厂\(i\)到工厂\(1\)的距离为\(X[i]\),求最小的总费用。

\(1<=n<=1000000\)

列出\(dp\)方程后发现\(“i"\)\(”j"\)的乘积,标准的斜率优化,注意这道题中用到的前缀和优化的技巧

#include <iostream>
#include <cstdio>
using namespace std;
#define maxn 1000100
#define ll long long
int n;
ll x[maxn];
int p[maxn], c[maxn];
ll spx[maxn], sp[maxn];
int q[maxn];
ll f[maxn];
ll X(int i) { return sp[i]; }
ll Y(int i) { return f[i] + spx[i]; }
int main()
{
    scanf("%d", &n);
    for(int i = 1; i <= n; i++)
    {
        scanf("%d%d%d", &x[i], &p[i], &c[i]);
        sp[i] = sp[i - 1] + p[i];
        spx[i] = spx[i - 1] + p[i] * x[i];
    }
    int l = 1, r = 1;
    q[1] = 0;
    for(int i = 1; i <= n; i++)
    {
       while(l < r && (Y(q[l + 1]) - Y(q[l])) <= x[i] * (X(q[l + 1]) - X(q[l])))l++;
       f[i] = f[q[l]] + (sp[i] - sp[q[l]]) * x[i] - (spx[i] - spx[q[l]]) + c[i];
       while(l < r && (Y(i) - Y(q[r])) * (X(q[r]) - X(q[r - 1])) <= (Y(q[r]) - Y(q[r - 1])) * (X(i) - X(q[r])))r--;
       q[++r] = i;
    }
    printf("%lld\n", f[n]);
    return 0;
}

特别行动队

题目链接

题意:有\(n\)个士兵组成的部队,把他们按顺序拆分成若干特别行动队调入战场,已知第\(i\)个士兵的初始战斗力为

\(x[i]\),一支特别行动队的初始战斗力\(x\)为队内士兵初始战斗力之和。初始战斗力\(x\)将按照以下公式进行修正:

\(x=ax^2+bx+c\),求一种编队方式使得所有特别行动队修正后的战斗力之和最大。求出这个最大和。

\(1<=n<=10^6 \,\, -5<=a<=-1\,\,\,\, |b|\,\,|c|<=10^7\,\,\, 1<=x[i]<=100\)

列出\(dp\)方程并转化后发现\(k\)为负数,单调递减,\(x\)为正数,单调递增,故用单调队列维护上凸包

#include <iostream>
#include <cstdio>
using namespace std;
#define ll long long
#define maxn 1000100
int n;
ll a, b, c;
ll x[maxn]={};
int q[maxn];
ll f[maxn]={};
ll X(int i) { return x[i]; }
ll Y(int i) { return f[i] + a * x[i] * x[i] - b * x[i]; }
int main()
{
    scanf("%d", &n);
    scanf("%lld%lld%lld", &a, &b, &c);
    for(int i = 1; i <= n; i++)
    {
        scanf("%lld", &x[i]);
        x[i] += x[i - 1];
    }
    int l = 1, r = 1;
    q[1] = 0;
    for(int i = 1; i <= n; i++)
    {
        while( l < r && (Y(q[l + 1]) - Y(q[l])) >= 2 * a * x[i] * (X(q[l + 1]) - X(q[l])) )l++;
        f[i]=Y(q[l]) - 2 * a * x[i] * X(q[l]) + a * x[i] * x[i] + b * x[i] +c;
       // f[i]=f[q[l]] + a * (x[q[l]] - x[i]) * (x[q[l]] - x[i]) - b * (x[q[l]] - x[i]) + c;
        while( l < r && (Y(i) - Y(q[r])) * (X(q[r]) - X(q[r - 1])) >= (Y(q[r]) - Y(q[r - 1])) * (X(i) - X(q[r])) )r--;
        q[++r]=i;
    }
    printf("%lld\n", f[n]);
    return 0;
}

锯木厂选址

题目链接

题意:与“仓库建设”大致相同,但只能建设两个仓库且建设仓库不需费用,且第\(n\)个工厂一定为仓库

做法为跑两个\(dp\)\(f\),\(g\)\(f[i]\)表示将第\(1\)个仓库修在i位置的最小运输费用

然后\(g[i]\)\(f[i]\)利用斜率优化转移即可。

#include <iostream>
#include <cstdio>
using namespace std;
#define maxn 20100
#define ll long long
int n;
ll sd[maxn], sp[maxn], sdp[maxn];
int q[maxn];
ll f[maxn], g[maxn];
ll X(int i) { return sp[i]; }
ll Y(int i) { return f[i] + sdp[i]; }
int main()
{
    scanf("%d", &n); n += 1;
    for(int i = 1; i <= n; i++)
    {
        int w, d;
        if(i != n)scanf("%d%d", &w, &d);
        sd[i + 1] = sd[i] + d;
        sp[i] = sp[i - 1] + w;
        sdp[i] = sdp[i - 1] + sd[i] * w;
    }
    for(int i = 1; i <= n; i++)
    {
        f[i] = sd[i] * sp[i - 1] - sdp[i - 1];
    }
    long long ans = 1e9; ans *= 1e9;
    int l = 1, r = 1; q[1] = 0;
    for(int i = 1; i <= n; i++)
    {
        while(l < r && (Y(q[l + 1]) - Y(q[l])) <= sd[i] * (X(q[l + 1]) - X(q[l])))l++;
        g[i] = f[q[l]] + sd[i] * (sp[i] - sp[q[l]]) - (sdp[i] - sdp[q[l]]);
        while(l < r && (Y(i) - Y(q[r])) * (X(q[r]) - X(q[r - 1]))<= (Y(q[r]) - Y(q[r - 1])) * (X(i) - X(q[r])))r--;
        q[++r] = i;
//        printf("j = %d g[%d] = %lld ans = %lld\n", q[l], i, g[i], g[i] + sd[n] * (sp[n - 1] - sp[i - 1]) - (sdp[n - 1] - sdp[i - 1]));
        ans = min(ans, g[i] + sd[n] * (sp[n - 1] - sp[i]) - (sdp[n - 1] - sdp[i]));
    }
    printf("%lld\n", ans);
    return 0;
}

征途

题目链接

题意:从S地到T第有n段路,把n段路分成m天走,使得每一天走过的路的方差最小。 设方差是v,可以证明v*m2是一个正整数,求最小方差 \(\times m^2\)

\(1<=n<=3000\)

这道题我一开始的做法是直接用\(double\)和斜率优化爆算,只得了\(50\)

发现因为算的次数很多且每次都用了平方,导致\(double\)的精度不够用。

然后看了题解,题目中说的“可以证明\(v\times m^2\)是一个正整数”,我并没有去用,而是直接用答案乘了一个m2,而正解应该是列出答案后化简发现只需利用斜率优化求出所有天的路程的平方和 * m,再减去总路程的平方即可,这道题告诉我们做题时应该运用到题目中给出的每一个条件,没有任何一个条件是无用的。

#include <iostream>
#include <cstdio>
using namespace std;
int n, m;
int d[3100], cnt = 0;
int q[3100], sx;
int dp[3100][3100];
int X(int i) { return d[i]; }
int Y(int i) { return dp[cnt][i] + d[i] * d[i] + 2 * sx * d[i]; }
int main()
{
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i++)
    {
        scanf("%d", &d[i]);
        d[i] += d[i - 1];
    }
    sx = 0;
    for(int i = 1; i <= n; i++)dp[1][i] = (d[i] - sx) * (d[i] - sx);
    for(int i = 2; i <= m; i++)
    {
        int l = 1, r = 1;
        q[1] = 0;
        cnt = i - 1;
        for(int j = 1; j <= n; j++)
        {
            while(l < r && (Y(q[l + 1]) - Y(q[l])) <= 2 * d[j] * (X(q[l + 1]) - X(q[l])))l++;
            dp[i][j] = dp[i - 1][q[l]] + (d[j] - d[q[l]] - sx) * (d[j] - d[q[l]] - sx);
            while(l < r && (Y(j) - Y(q[r])) * (X(q[r]) - X(q[r - 1]))<= (Y(q[r]) - Y(q[r - 1])) * (X(j) - X(q[r])))r--;
            q[++r] =j;
        }
   //     printf("i = %d l = %d r = %d\n", i, l, r);
    }
   //for(int i = 1; i <= n; i++)printf("d[%d] = %d\n", i, d[i]);
  /*  printf("sx = %d\n", sx);
    for(int i = 1; i <= m; i++)
    {
        for(int j = 1; j <= n; j++)printf("f[%d][%d] = %d\n",i,j,dp[i][j]);
    }*/
    printf("%d\n",dp[m][n] * m - d[n] * d[n]);
    return 0;
}

转载于:https://www.cnblogs.com/Akaina/p/11427137.html

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: hdu 2829 Lawrence 斜率优化dp 这道题是一道经典的斜率优化dp题目,需要用到单调队列的思想。 题目大意是给定一个序列a,求出一个序列b,使得b[i]表示a[1]~a[i]中的最小值,且满足b[i] = min{b[j] + (i-j)*k},其中k为给定的常数。 我们可以将上式拆开,得到b[i] = min{b[j] - j*k} + i*k,即b[i] = i*k + min{b[j] - j*k},这个式子就是斜率优化dp的形式。 我们可以用单调队列来维护min{b[j] - j*k},具体思路如下: 1. 首先将第一个元素加入队列中。 2. 从第二个元素开始,我们需要将当前元素加入队列中,并且需要维护队列的单调性。 3. 维护单调性的方法是,我们从队列的末尾开始,将队列中所有大于当前元素的元素弹出,直到队列为空或者队列中最后一个元素小于当前元素为止。 4. 弹出元素的同时,我们需要计算它们对应的斜率,即(b[j]-j*k)/(j-i),并将这些斜率与当前元素的斜率比较,如果当前元素的斜率更小,则将当前元素加入队列中。 5. 最后队列中的第一个元素就是min{b[j] - j*k},我们将它加上i*k就得到了b[i]的值。 6. 重复以上步骤直到处理完所有元素。 具体实现可以参考下面的代码: ### 回答2: HDU 2829 Lawrence 斜率优化 DP 是一道经典的斜率优化 DP 题目,其思想是通过维护一个下凸包来优化 DP 算法。下面我们来具体分析一下这道题目。 首先,让我们看一下该题目的描述。题目给定一些木棒,要求我们将这些木棒割成一些给定长度,且要求每种长度的木棒的数量都是一样的,求最小的割枝次数。这是一个典型的背包问题,而且在此基础上还要求每种长度的木棒的数量相同,这就需要我们在状态设计上走一些弯路。 我们来看一下状态的定义。定义 $dp[i][j]$ 表示前 $i$ 个木棒中正好能割出 $j$ 根长度为 $c_i$ 的木棒的最小割枝次数。对于每个 $dp[i][j]$,我们可以分类讨论: 1. 不选当前的木棒,即 $dp[i][j]=dp[i-1][j]$; 2. 选当前的木棒,即 $dp[i][j-k]=dp[i-1][j-k]+k$,其中 $k$ 是 $j/c_i$ 的整数部分。 现在问题再次转化为我们需要在满足等量限制的情况下,求最小的割枝次数。可以看出,这是一个依赖于 $c_i$ 的限制。于是,我们可以通过斜率优化 DP 来解决这个问题。 我们来具体分析一下斜率优化 DP 算法的思路。我们首先来看一下动态规划的状态转移方程 $dp[i][j]=\min\{dp[i-1][k]+x_k(i,j)\}$。可以发现,$dp[i][j]$ 的最小值只与 $dp[i-1][k]$ 和 $x_k(i,j)$ 有关。其中,$x_k(i,j)$ 表示斜率,其值为 $dp[i-1][k]-k\times c_i+j\times c_i$。 接下来,我们需要维护一个下凸包,并通过斜率进行优化。我们具体分析一下该过程。假设我们当前要计算 $dp[i][j]$。首先,我们需要找到当前点 $(i,j)$ 在凸包上的位置,即斜率最小值的位置。然后,我们根据该位置的斜率计算 $dp[i][j]$ 的值。接下来,我们需要将当前点 $(i,j)$ 加入到下凸包上。 我们在加入点的时候需要注意几点。首先,我们需要将凸包中所有斜率比当前点小的点移除,直到该点能够加入到凸包中为止。其次,我们需要判断该点是否能够加入到凸包中。如果不能加入到凸包中,则直接舍弃。最后,我们需要保证凸包中斜率是单调递增的,这就需要在加入新的点之后进行上一步操作。 以上就是该题目的解题思路。需要注意的是,斜率优化 DP 算法并不是万能的,其使用情况需要根据具体的问题情况来确定。同时,该算法中需要维护一个下凸包,可能会增加一些算法的复杂度,建议和常规 DP 算法进行对比,选择最优的算法进行解题。 ### 回答3: 斜率优化DP是一种动态规划优化算法,其主要思路是通过对状态转移方程进行变形,提高算法的时间复杂度。HDU2829 Lawrence问题可以用斜率优化DP解决。 首先,我们需要了解原问题的含义。问题描述如下:有$n$个人在数轴上,第$i$个人的位置为$A_i$,每个人可以携带一定大小的行李,第$i$个人的行李重量为$B_i$,但是每个人只能帮助没有他们重量大的人搬行李。若第$i$个人搬运了第$j$个人的行李,那么第$i$个人会累加$C_{i,j}=\left|A_i-A_j\right|\cdot B_j$的体力消耗。求$m$个人帮助每个人搬运行李的最小体力消耗。 我们可以通过斜率优化DP解决这个问题。记$f_i$为到前$i$个人的最小体力消耗,那么状态转移方程为: $$f_i=\min_{j<i}\{f_j+abs(A_i-A_j)\cdot B_i\}$$ 如果直接使用该方程,时间复杂度为$O(n^2)$,如果$n=10^4$,则需要计算$10^8$次,运算时间极长。斜率优化DP通过一些数学推导将方程变形,将时间复杂度降低到$O(n)$,大大缩短了计算时间。 通过斜率优化DP的推导式子,我们可以得到转移方程为: $$f_i=\min_{j<i}\{f_j+slope(j,i)\}$$ 其中,$slope(j,i)$表示直线$j-i$的斜率。我们可以通过如下方式来求解$slope(j,i)$: $$slope(j,i)=\frac{f_i-f_j}{A_i-A_j}-B_i-B_j$$ 如果$slope(j,i)\leq slope(j,k)$,那么$j$一定不是最优,可以直接舍去,降低计算时间。该算法的时间复杂度为$O(n)$。 综上所述,斜率优化DP是一种动态规划优化算法,可以大大缩短计算时间。在处理类似HDU2829 Lawrence问题的时候,斜率优化DP可以很好地解决问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值