• 斜率优化学习笔记


    斜率优化学习笔记

    (解释太麻烦,这里笼统的讲一下,具体看书,书上更详尽,所以无视这部分)

    斜率优化通常一般可以将(n^2)的DP优化到(O(n)) ,是一个很强的优化。

    形如:

    [f[i]=min{f[j]+(s[i]-s[j])^2}(i-dleq j) ]

    看似好像可以用单调队列的,但是无法把(i,j)分开的DP,大部分可以用斜率优化

    一般斜率优化具有单调性,可以用单调队列维护有效决策;

    单调性什么意思?有效决策什么意思?

    例如这个方程,把它变一下,(min)去掉,把关于(j)的都移到左边,关于(i)(i,j)的移到右边

    [f[j]+s[j]^2=2*s[i]*s[j]+f[i]-s[i]^2 ]

    (s[i]​)都是已知的,要在一堆({f[j],s[j]}​)中选出一个使得(f[i]​)最小;

    那么对于每一个({f[j],s[j]}) ,放在平面直角坐标系中设(f[j]+s[j]^2)(y)(s[j])(x) ,可以画出一条斜率为(2*s[i])的直线,那么这条线的截距就为(f[i]-s[i]^2)

    那么使得截距最小的({f[j],s[j]}) 就为所求。

    所以决策就是这些({f[j],s[j]}) ,而有效决策就是对求(f[i])有贡献的决策。

    这里(s[i])单调递增,就因为着斜率单调递增,每次新加入的决策也在最右边。

    然后结合书上讲的有些关于凹凸的东西,直线平移,什么的,看书。(滑稽)

    (ps: 加黑的都是斜率优化的套路)

    列出斜率方程后就基本上可以直接套板子了;


    任务安排2

    一开始想到暴力DP方程:(设(f[i][j])为前(j)个任务分为(i)批的最小费用,(sum)为各种前缀和)

    [f[i][j]=min{f[i-1][k]+(S*i+sumT[j])*(sumC[j]-sumC[k])} ]

    拆开也是可以斜率优化的,因为拆开后只会得到一项(j,k)无法分开的,这是可以套板子的,但是(i)是没有限制的,所以难以得出答案;

    那么考虑当每分一个组的时候后面的时间都会往后移(S) ,那么最后总费用就会增加(S*(sumC[n]-sumC[j])) ,那就好办了,设(f[i])为前(i)个任务的最小费用,就有状态转移方程:

    [f[i]=min{f[j]+sumT[i]*(sumC[i]-sumC[j])+S*(sumC[n]-sumC[i])} ]

    其实这里(f[i])的定义有误,应该为前(i)个任务对最后答案的最小贡献。(无视这句话也可以写)

    转化为斜率方程:

    [f[j]=(S+sumT[i])*sumC[j]+f[i]-sumT[i]*sumC[i]-S*sumC[n] ]

    这里斜率和加入点的横坐标是单调递增的,然后又可以开心的套板子了。


    任务安排3

    和上题不一样的地方就是列出的斜率方程,斜率没有单调性,但是有效决策的横坐标的加入还是单调递增的,那就二分查找最优的决策就可以了。

    #include<iostream>
    #include<cstdio>
    #define ll long long
    using namespace std;
    const int N=3e5+5;
    ll f[N],n,s;
    ll sumc[N],sumt[N];
    ll head=1,tail=1;
    ll q[N];
    ll read()
    {
        ll f=1;
        char ch;
        while((ch=getchar())<'0'||ch>'9') if(ch=='-') f=-1;
        ll res=ch-'0';
        while((ch=getchar())>='0'&&ch<='9') res=res*10+ch-'0';
        return res*f;
    }
    ll check(int i)
    {
        if(head==tail) return q[head];
        int l=head,r=tail;
        while(l<r)
        {
            int mid=(l+r)>>1;
            if(f[q[mid+1]]-f[q[mid]]<=(s+sumt[i])*(sumc[q[mid+1]]-sumc[q[mid]])) l=mid+1;
            else r=mid;
        }
        return q[r];
    }
    void solve(int i)
    {
        ll j=check(i);
        f[i]=f[j]-(s+sumt[i])*sumc[j]+sumt[i]*sumc[i]+sumc[n]*s;
        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;
    }
    int main()
    {
    #ifndef ONLINE_JUDGE
        freopen("task.in","r",stdin);
        freopen("task.out","w",stdout);
    #endif
        n=read(),s=read();
        for(int i=1;i<=n;i++)
        {
            ll t=read(),c=read();
            sumt[i]=sumt[i-1]+t;
            sumc[i]=sumc[i-1]+c;
        }
        for(int i=1;i<=n;i++) solve(i);
        printf("%lld
    ",f[n]);
        return 0;
    }
    

    Cats Transport

    这题有个巧妙的思路(巧妙的思路当然不是我想出来的啊)(a[i]=t[i]-sum_{j=1} ^i d[j]) ,这样就可以表示出如果饲养员要接到第(i)只猫就要在(a[i])之后出发,且猫(i)等待的时间为(t-a[i])

    那么这样就好办了,只要将(a[i])从小到大排序,求出前缀和(suma[i]),那要带走区间([l+1,r])的猫,这些猫要等待待的时间为(a[r]*(r-l)-(suma[r]-suma[l]))

    这样就有状态转移方程:

    [f[i][j]=min{f[i-1,k]+a[j]*(j-k)-(suma[j]-suma[k])} ]


    玩具装箱

    直接划分就好了,设(f[i])为前(i)个的最小价值,(Sum[i])为加上了填充物之后的长度的前缀和,即(Sum[i]=Sum[i-1]+c[i]+1) ,这样就有DP方程:

    [f[i]=min{f[j]+(Sum[i]-(Sum[j]+1)-L)^2} ]

    为了简化式子,再设(S[j]=Sum[j]+1) ,然后去掉(min)把这个式子变为斜率优化通常的亚子:

    [f[j]+S[j]^2+2*L*S[j]=2*Sum[i]*S[j]+f[i]-(Sum[i]-L)^2 ]

    然后以此建立坐标系,方程左边为(y)(S[j])(x) ,然后每条线斜率为(2*Sum[i])


    仓库建设

    为了把最后得到的斜率方程使得斜率和(x)都单调递增,我尝试了一堆表示方法(甚至一度怀疑是不是又要二分了),不过最后还是在我不懈的努力下列出来了(哭泣)

    由题意可得最后一个工厂一定要设置仓库,所以可以设(f[i])为前(i)个工厂的最小花费,且第(i)个工厂建设仓库,这样(f[n]) 就是最后的答案;

    下面就是我想了很久的东西了:(ps:(d[i])为题目中的(x[i])

    对于每个工厂,(d[i]*p[i])为每个工厂送到工厂1所需要的费用,那么设(sumf[i])为这费用的前缀和,然后再设(sump[i])(p[i])的前缀和,所以对于区间([l,r])的物品全部运到(r)所需要的费用为(d[r]*(sump[r]-sump[l])-(sumf[r]-sumf[l])) ,可以通过画图理解;

    所以有状态转移方程:

    [f[i]=min{f[j]+d[i]*(sump[i]-sump[j])-(sumf[i]-sumf[l])+c[i]} ]

    变成斜率优化的方程,把关于(j)的都移到左边,关于(i)(i,j)的移到右边:

    [f[j]+sumf[j]=d[i]*sump[j]+f[i]-d[i]*sump[i]+sumf[i]-c[i] ]

    斜率为(d[i])(x)(sump[j]) ,他们都单调递增,那就写完了,套板子就好了。

    #include<iostream>
    #include<cstdio>
    #define ll long long
    using namespace std;
    const int N=1e6+5;
    int n;
    ll sumf[N],sump[N],d[N],c[N],f[N];
    ll q[N],x[N],y[N];
    ll read()
    {
        ll f=1;
        char ch;
        while((ch=getchar())<'0'||ch>'9') if(ch=='-') f=-1;
        ll res=ch-'0';
        while((ch=getchar())>='0'&&ch<='9') res=res*10+ch-'0';
        return res*f;
    }
    int main()
    {
    #ifndef ONLINE_JUDGE
        freopen("publi.in","r",stdin);
        freopen("publi.out","w",stdout);
    #endif
        n=read();
        for(int i=1;i<=n;i++)
        {
            d[i]=read();
            ll p=read();
            c[i]=read();
            sumf[i]=sumf[i-1]+d[i]*p;
            sump[i]=sump[i-1]+p;
        }
        int head=1,tail=1;
        for(int i=1;i<=n;i++)
        {
            while(head<tail&&(y[head+1]-y[head])<=d[i]*(x[head+1]-x[head])) head++;
            f[i]=f[q[head]]+d[i]*(sump[i-1]-sump[q[head]])-(sumf[i-1]-sumf[q[head]])+c[i];
            while(head<tail&&(f[i]+sumf[i]-y[tail])*(x[tail]-x[tail-1])<=(y[tail]-y[tail-1])*(sump[i]-x[tail])) tail--;
            q[++tail]=i,y[tail]=f[i]+sumf[i],x[tail]=sump[i];
        }
        printf("%lld",f[n]);
        return 0;
    }
    

    特别行动队

    与前几题不也一样的地方就是斜率递减,但(x)还是递增;还是可以用单调队列,维护一个单调递减的斜率;在匹配斜率的时候只需要找到一个斜率比它小的就可以了;

    DP方程:

    [f[i]=max{f[j]+a*(sum[i]-sum[j])^2+b*(sum[i]-sum[j])+c} ]

    后面还是板子;


    打印文章

    板子;


    锯木厂选址

    和仓库建设类似,不过更简单,先(O(n))算出第一个锯木厂对于前(i)颗树的最优策略,然后求二个锯木厂最优策略就和仓库建设的思路一样了,最后再(O(n))枚举一下第二个锯木厂的位置,后面的都运到山底去,取个(min)就完事了;


    任务安排4

    以后要写的题。

    点的加入和斜率都不具有单调性,需要动态加点,那就需要平衡树维护,具体怎么操作,这里留个坑,等我写完了再补回来。

  • 相关阅读:
    Median Value
    237. Delete Node in a Linked List
    206. Reverse Linked List
    160. Intersection of Two Linked Lists
    83. Remove Duplicates from Sorted List
    21. Merge Two Sorted Lists
    477. Total Hamming Distance
    421. Maximum XOR of Two Numbers in an Array
    397. Integer Replacement
    318. Maximum Product of Word Lengths
  • 原文地址:https://www.cnblogs.com/Wednesday-zfz/p/13777569.html
Copyright © 2020-2023  润新知