Optimization summarized slope DP

Optimization summarized slope DP

Task scheduling 1

First \ (O (N ^ 2) \) approach, respectively, to a pretreatment \ (T_i, C_i \) prefix and \ (T [I], C [I] \) , where \ (f [i ] \) before does not mean \ (i \) time spent on tasks, but shell with all previous decisions to influence the latter. This question dp idea is to side edge plus the current decision-making influence on the decision-making behind (a kind of "advance fee calculation" thinking)

Transfer equation:
\ [F [I] = min (F [J] + (C [I] -C [J]) * T [I] + S * (C [n-] -C [J]), F [ I]) \]

#include <cstdio>
#include <cstring>
#define MIN(A,B) ((A)<(B)?(A):(B))
#define MAXN 5005
using namespace std;
int f[MAXN];
int sumt[MAXN],sumc[MAXN];
int n,s;
int main(){
    memset(f, 0x3f, sizeof(f));
    f[0]=0;
    scanf("%d\n%d", &n, &s);
    for(int i=1;i<=n;++i){
        int t,c;
        scanf("%d %d", &t, &c);
        sumt[i]=sumt[i-1]+t;
        sumc[i]=sumc[i-1]+c;
    }
    for(int i=1;i<=n;++i)
    for(int j=0;j<i;++j){
        f[i]=MIN(f[j]+(sumc[i]-sumc[j])*sumt[i]+s*(sumc[n]-sumc[j]), f[i]);
    }
    printf("%d", f[n]);
    return 0;
}

Task Scheduler 2

The original transfer equation transposition, so with only \ (j \) formula to the left, the other to the right, then to the right with \ (j \) equation to mention common factor to obtain
\ [f [j ] = c [j] \ times
(t [i] + s) + f [i] -s \ times c [n] -c [i] \ times t [i] \] At this time, the outer loop to \ (I \) regarded as a known amount (on the \ (I \) variable \ (t [i], c [i] \) is a constant), seen as a function of a determined slope \ ( KX + B = Y \) , where \ (f [j] \) seen as a function of \ (Y \) , \ (C [J] \) seen as a function of \ (X \) , \ ((t [i] + s) \) is the slope. Let \ (f [i] \) minimum that is to make this a function intercept minimum.

(From FIG ButterflyDew )

Easily obtained: determining the first point of this slope function from the lowermost face upward translational \ ((c [j], f [j]) \) is the intercept can take to the minimum (most \ (F [ I] \) ) points (satisfying \ (k_1 \ Le k_0 \ Le K_2 \) ), as shown, is a vertex of the convex hull.

而像这样的上凸壳顶点如图显然一定不会是最优解,所以我们维护一个下凸壳,每次找到顶点即为最优

而这道题我们不需要维护整个下凸壳,而只维护下凸壳顶点及顶点右侧的点(因为左侧不可能比顶点优)。我们维护一个单调队列,使队首为下凸壳顶点,每次最优即为队首元素。维护时先将下凸壳顶点左边的所有点弹掉(若当前点与下一个点构成的直线斜率小于等于\(s+t[i]​\),则当前点在左边),之后队首就是下凸壳的顶点,取出并算出的\(f[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]]+t[i]*c[i]+S*c[n]-c[q[l]]*(S+t[i]);
while(l<r&&(f[i]-f[q[r]])*(c[q[r]]-c[q[r-1]])<=(f[q[r]]-f[q[r-1]])*(c[i]-c[q[r]])) r--; // 维护下凸壳
q[++r]=i;

完整代码:

#include <cstdio>
#include <cstring>
#define MIN(A,B) ((A)<(B)?(A):(B))
#define MAXN 5005*2
using namespace std;
int f[MAXN],q[MAXN];
int t[MAXN],c[MAXN];
int n,s;
int main(){
    memset(f, 0x3f, sizeof(f));
    f[0]=0;
    scanf("%d\n%d", &n, &s);
    for(int i=1;i<=n;++i){
        int tt,tc;
        scanf("%d %d", &tt, &tc);
        t[i]=t[i-1]+tt;
        c[i]=c[i-1]+tc;
    }
    int l=1,r=1;
    for(int i=1;i<=n;++i){
        while(l<r&&(f[q[l+1]]-f[q[l]])<=(t[i]+s)*(c[q[l+1]]-c[q[l]])) l++;
        f[i]=f[q[l]]-c[q[l]]*(t[i]+s)+s*c[n]+c[i]*t[i];
        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;
    }
    printf("%d", f[n]);
    return 0;
}

任务安排3

因为\(T_i\)可能为负数,所以直线斜率\(s+t[i]\)不单增,所以不能直接单调队列取队首,而是二分查找出两条斜率为\(k_1,k_2\)的直线满足\(k_1\le s+t[i]\le k_2​\),两条直线交点即为最优点。

#include <cstdio>
#include <cstring>
#define MIN(A,B) ((A)<(B)?(A):(B))
#define MAXN 300003
#define ll long long
using namespace std;
ll f[MAXN];
int q[MAXN];
ll t[MAXN],c[MAXN];
int n,s;
int l=1,r=1;
inline int query(ll k){
    int L=l,R=r,ans;
    while(L<=R){
        int mid=(L+R)>>1;
        if((f[q[mid]]-f[q[mid-1]])<=k*(c[q[mid]]-c[q[mid-1]])) L=mid+1,ans=mid;
        else R=mid-1;
    }
    return q[ans];
}
int main(){
    memset(f, 0x3f, sizeof(f));
    f[0]=0;
    scanf("%d\n%d", &n, &s);
    for(int i=1;i<=n;++i){
        ll tt,tc;
        scanf("%lld %lld", &tt, &tc);
        t[i]=t[i-1]+tt;
        c[i]=c[i-1]+tc;
    }
    for(int i=1;i<=n;++i){
        int j=query(t[i]+s);
        f[i]=f[j]-c[j]*(t[i]+s)+s*c[n]+c[i]*t[i];
        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;
    }
    printf("%lld", f[n]);
    return 0;
}

百日旅行

\(N\)天,如果连续\(x\)天旅游,则花费\(p\times x\times x\),否则连续\(x\)天吃饭,花费\(Q\times x\),问最小花费

设计dp,\(f[i]\)表示第\(i\)天最小花费并且这一天在旅游,\(g[i]\)表示在这一天吃饭,转移方程:

f[i]=min(g[j]+SQR(i-j)*p,f[i])
g[i]=min(f[j]+(i-j)*q,g[i])

观察\(g[i]\)\(g[i]=i*q+f[j]-j*q\),所以维护\(1\)\(i-1\)\(f[i]-i*p\)最小值即可。

观察\(f[i]​\)\(f[i]=g[i]+p*i^2-2*p*i*j+p*j^2​\)

移项,使只含\(j\)的式子在左边,其余右边,得\(g[j]+p*j^2=(i*2*p)*j+f[i]-p*i^2\)

此时看做一个一次函数,要想\(f[i]​\)最小,即让截距最小,维护一个下凸壳。又发现斜率\(i​\)单增,可使用单调队列。

#include <cstdio>
#define ll long long
#define MAXN 200002
#define MIN(A,B) ((A)<(B)?(A):(B))
#define j (q[l])
#define SQR(A) ((A)*(A))
#define Y(A) (g[A]+P*(A)*(A))
using namespace std;
int n,l,r;
ll f[MAXN],g[MAXN],q[MAXN],P,Q;
int main(){
    scanf("%d %lld %lld", &n, &P, &Q);
    l=1,r=1;
    ll t=0;
    for(int i=1;i<=n;++i){
        while(l<r&&(Y(q[l + 1]) - Y(q[l])) <= i*2*P * (q[l+1] - q[l])) ++l;
        f[i]=g[j]+P*SQR(j)+P*SQR(i)-i*2*P*j;
        g[i]=i*Q+t;
        t=MIN(f[i]-i*Q,t);
        while(l<r && (Y(q[r]) - Y(q[r-1]))*(q[i] - q[r]) >= (Y(q[i]) - Y(q[r]))*(q[r] - q[r-1])) --r;
        q[++r]=i;
    }
    printf("%lld", MIN(f[n], g[n]));
    return 0;
}
/*
g[i]=i*q+f[j]-j*q

g[j]+p*j^2=(i*2*p)*j+f[i]-p*i^2
*/

Guess you like

Origin www.cnblogs.com/santiego/p/11470092.html