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的做法也适应不了。

寒假回来 斜率优化大致懂了维护凸包什么的是关键所以代码中有解释自己打的哦!

//#include<bits/stdc++.h>
#include<iostream>
#include<cstdio>
#include<iomanip>
#include<cstring>
#include<string>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<cctype>
#include<utility>
#include<set>
#include<bitset>
#include<queue>
#include<stack>
#include<deque>
#include<map>
#include<vector>
#include<ctime>
#define INF 168430090000ll
using namespace std;
char buf[1<<15],*fs,*ft;
inline char getc()
{
    return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;
}
inline long long read()
{
    long long x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
inline void put(long long x)
{
    x<0?putchar('-'),x=-x:0;
    long long num=0;char ch[50];
    while(x)ch[++num]=x%10+'0',x/=10;
    num==0?putchar('0'):0;
    while(num)putchar(ch[num--]);
    putchar('\n');return;
}
const long long MAXN=10002;
long long n,s;
long long f[MAXN];//f[i]表示第i个任务完成所需最小费用
long long T[MAXN],F[MAXN],q[MAXN<<1],l=0,r=0;
int main()
{
    //freopen("1.in","r",stdin);
    n=read();s=read();
    for(long long i=1;i<=n;i++)
    {
        T[i]=read();
        F[i]=read();
        T[i]+=T[i-1];
        F[i]+=F[i-1];
    }
    memset(f,10,sizeof(f));
    f[0]=0;q[l]=0;
    for(long long i=1;i<=n;i++)//枚举第i个任务
        {
            long long B=T[i]*F[i]+s*F[n];
            long long K=T[i]+s;
            while(l<r&&((f[q[l+1]]-f[q[l]])<=K*(F[q[l+1]]-F[q[l]])))l++;
            f[i]=f[q[l]]-K*F[q[l]]+B;
            while(l<r&&(f[q[r]]-f[q[r-1]])*(F[i]-F[q[r]])>=(f[i]-f[q[r]])*(F[q[r]]-F[q[r-1]]))r--;
            //注意 除法变成乘法 大于等于号方向别搞反
            q[++r]=i;
            /*for(long long j=0;j<i;j++)//枚举前面的那个任务
            {
                //f[i]=min(f[i],f[j]+T[i]*(F[i]-F[j])+s*(F[n]-F[j]));
                //f[i]=min(f[i],f[j]-T[i]*F[j]+T[i]*F[i]+s*F[n]-s*F[j]);
                ans=min(ans,f[j]-K*F[j]);
                //K显然是斜率 每一个f[i]的求出都是以F[j]为变量对应一个f[j]
                //显然当斜率......想通就可以
                f[i]=ans+B;f[i]=f[j]-K*F[j]+B;
                f[j]=f[i]-B+k*f[j];显然这是一个点斜式
                //截距为f[i]-B;截距越小f[i]越小
            }*/
        }
    put(f[n]);
    return 0;
}

然后我们会发现一个东西就是我不断出队队首的原因 也就是寻找一个点 (设点a,b,c,此时斜率为k,k>kab,k<kac)b这个点就是最优转移决策。

这个东西是一定的呢!蒟蒻不会证明但是会画图 把所有情况画出来 发现了或者是“显然”的发现了这一特异性的结论。

例题:

 

这里的T 有可能是负数,那么我们的斜率T[i]+s就不再具有单调性如果像上述方法直接把队首出队的话那么当前斜率可能依赖之前队首的点

所以这里我们要维护整个下凸的凸包然后每次通过在这个凸包上二分来寻找最优决策。

二分不好写 RE WA 很难调但是坚持 就能调对。

//#include<bits/stdc++.h>
#include<iostream>
#include<cstdio>
#include<iomanip>
#include<cstring>
#include<string>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<cctype>
#include<utility>
#include<set>
#include<bitset>
#include<queue>
#include<stack>
#include<deque>
#include<map>
#include<vector>
#include<ctime>
#define INF 168430090
using namespace std;
char buf[1<<15],*fs,*ft;
inline char getc()
{
    return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;
}
inline long long read()
{
    long long x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
inline void put(long long x)
{
    x<0?putchar('-'),x=-x:0;
    long long num=0;char ch[50];
    while(x)ch[++num]=x%10+'0',x/=10;
    num==0?putchar('0'):0;
    while(num)putchar(ch[num--]);
    putchar('\n');return;
}
//任务安排升级版 T可能为负值 RE:应将除法变成乘法小心0
const long long MAXN=300002;
long long n,s;
long long T[MAXN],F[MAXN];
long long f[MAXN];//f[i]表示第i个任务完成所需最小时间
long long q[MAXN],h,t;
int g[MAXN],l,r;
long long find(long long x)
{
    if(h==t)return h;
    long long l=h,r=t;
    while(l+1<r)
    {
        long long mid=(l+r)>>1;
        int tmp1=(F[q[mid]]-F[q[mid-1]]);
        int tmp2=(F[q[mid+1]]-F[q[mid]]);
        long long K1=((f[q[mid]]-f[q[mid-1]]));
        long long K2=((f[q[mid+1]]-f[q[mid]]));
        if(K1<x*tmp1&&K2>x*tmp2)return mid;
        if(K1<x*tmp1&&K2<x*tmp2)l=mid;
        else r=mid;
    }
    int tmp=(F[q[r]]-F[q[l]]);
    long long K3=((f[q[r]]-f[q[l]]));
    if(K3<x*tmp)return r;
    if(K3>x*tmp)return l;
    return r;
}
int main()
{
    freopen("1.in","r",stdin);
    n=read();s=read();
    for(long long i=1;i<=n;i++)
    {
        T[i]=read();
        T[i]+=T[i-1];
        F[i]=read();
        F[i]+=F[i-1];
    }
    for(long long i=1;i<=n;i++)
    {
        long long K=T[i]+s;
        long long B=F[i]*T[i]+s*F[n];
        long long tmp=find(K);
        //cout<<tmp<<endl;
        f[i]=f[q[tmp]]-F[q[tmp]]*K+B;
        while(h<t&&((f[q[t]]-f[q[t-1]])*(F[i]-F[q[t]]))>=((f[i]-f[q[t]])*(F[q[t]]-F[q[t-1]])))t--;
        q[++t]=i;
    }
    put(f[n]);
    return 0;
}

要走的路还长。

posted @ 2019-01-20 21:47  chdy  阅读(255)  评论(0编辑  收藏  举报