dp的斜率优化

  对于刷题量我觉得肯定是刷的越多越好(当然这是对时间有很多的人来说。

但是在我看来我的确适合刷题较多的那一类人,应为我对知识的应用能力并不强。这两天学习的内容是dp的斜率优化。当然我是不太会的。

这个博文肯定也是不断更新的(随着我对斜率优化的不断深入的理解。

这两天做的题是一道经典的任务安排。

这道题是我两个星期前都想a掉的题。然后都是一个最简单的状态转移方程都推不出,真的是菜到家了。

然后翻书有一个最简单的状态转移方程,看了几分钟发现看的不是很懂而且我感觉这题应该自己思考。

所以扔下来了。两个星期回来再写,发现自己有了思路。

推了一个状态转移方程拿了60分比较骄傲,因为自己的dp很垃圾,现在我觉得才刚入门。

const int MAXN=5002;
int n,s;
int c[MAXN],t[MAXN];
int f[MAXN][MAXN],ans=INF;
//设f[i][j]表示前i个任务分成j组所能得到的最小值。目标min{f[n][i] i->1~n};
int main()
{
    //freopen("1.in","r",stdin);
    n=read();s=read();
    for(int i=1;i<=n;i++)t[i]=read(),c[i]=read(),t[i]+=t[i-1],c[i]+=c[i-1];
    memset(f,10,sizeof(f));
    f[0][0]=0;
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=i;j++)
        {
            for(int k=1;k<=i;k++)//枚举这一组选择几项任务
            {
                f[i][j]=min(f[i][j],f[i-k][j-1]+(s*j+t[i])*(c[i]-c[i-k]));
            }
        }
    }
    for(int i=1;i<=n;i++){ans=min(ans,f[n][i]);}
    put(ans);
    return 0;
}

这是自己打的代码能拿60分欸。复杂度n^3 超级高应为n<=MAXN 

水了60心满意足。然后可以心安理得的翻书,书上和我定义的state不太一样。

const int MAXN=5002;
int n,s;
int c[MAXN],t[MAXN];
int f[MAXN][MAXN],ans=INF;
//设f[i][j]表示前i个任务分成j组所能得到的最小值。目标min{f[n][i] i->1~n};
int main()
{
    //freopen("1.in","r",stdin);
    n=read();s=read();
    for(int i=1;i<=n;i++)t[i]=read(),c[i]=read(),t[i]+=t[i-1],c[i]+=c[i-1];
    memset(f,10,sizeof(f));
    f[0][0]=0;
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=i;j++)
        {
            for(int k=0;k<i;k++)//枚举上一组选取了多少任务
            {
                f[i][j]=min(f[i][j],f[k][j-1]+(s*j+t[i])*(c[i]-c[k]));
            }
        }
    }
    for(int i=1;i<=n;i++){ans=min(ans,f[n][i]);}
    put(ans);
    return 0;
}

当然实现起来细节地方写自己的状态转移方程其实都是一样的。重点是状态不一样,值得借鉴。

然后考虑优化则么优化呢?经过书上的短短几行的点播。

它给出了一个其实不用枚举多少组,直接省掉一个for 和一个维度。比较好的思路。

书上说是费用提前计算的经典思想。

const long long MAXN=5002,INF=21474836474836ll;
long long n,s;
long long c[MAXN],t[MAXN];
long long f[MAXN];
//f[i]表示前i个任务分成过若干批完成执行的最小费用
int main()
{
    //freopen("1.in","r",stdin);
    n=read();s=read();
    for(long long i=1;i<=n;i++)t[i]=read(),c[i]=read(),t[i]+=t[i-1],c[i]+=c[i-1];
    for(long long i=0;i<=n;i++)f[i]=INF;
    f[0]=0;
    for(long long i=1;i<=n;i++)
    {
        for(long long j=0;j<i;j++)
        {
            f[i]=min(f[i],f[j]+t[i]*(c[i]-c[j])+s*(c[n]-c[j]));
        }
    }
    put(f[n]);
    return 0;
}

这样的话就可以实现了分成多少组和当前这一组选多少个物品的结合。

这样的话很显然是比较优秀的做法。骤降一个维度和一个维度的时间。这种思想不错!

关键是数据范围还有更大的。n^2的做法也适应不了。

这里就有的一个斜率优化的思想,当然本人还没完全理解。理解的时候再写吧。

const long long MAXN=500002,INF=21474836474836ll;
long long n,s;
long long c[MAXN],t[MAXN];
long long f[MAXN],q[MAXN<<1],l=1,r=1;
//f[i]表示前i个任务分成过若干批完成执行的最小费用
int main()
{
    //freopen("1.in","r",stdin);
    n=read();s=read();
    for(long long i=1;i<=n;i++)t[i]=read(),c[i]=read(),t[i]+=t[i-1],c[i]+=c[i-1];
    for(long long i=0;i<=n;i++)f[i]=INF;
    f[0]=0;q[1]=0;
    for(long long i=1;i<=n;i++)
    {
        while(l<r&&(f[q[l+1]]-f[q[l]])<=(s+t[i])*(c[q[l+1]]-c[q[l]]))l++;
        f[i]=f[q[l]]-(s+t[i])*c[q[l]]+t[i]*c[i]+s*c[n];
        while(l<r&&((f[q[r]]-f[q[r-1]])*(c[i]-c[q[r]])>=(f[i]-f[q[r]])*(c[q[r]]-c[q[r-1]])))r--;
        q[++r]=i;
    }
    put(f[n]);
    return 0;
}

O(n)直接秒,比较好的思路啊。

要走的路还长。

猜你喜欢

转载自www.cnblogs.com/chdy/p/10296319.html
今日推荐