Bootstrap

斜率优化dp:

一、任务安排1:
有 N 个任务排成一个序列在一台机器上等待执行,它们的顺序不得改变。

机器会把这 N 个任务分成若干批,每一批包含连续的若干个任务。

从时刻0开始,任务被分批加工,执行第 i 个任务所需的时间是 Ti。

另外,在每批任务开始前,机器需要 S 的启动时间,故执行一批任务所需的时间是启动时间 S 加上每个任务所需时间之和。

一个任务执行后,将在机器中稍作等待,直至该批任务全部执行完毕。

也就是说,同一批任务将在同一时刻完成。

每个任务的费用是它的完成时刻乘以一个费用系数 Ci。

请为机器规划一个分组方案,使得总费用最小。

输入格式
第一行包含整数 N。

第二行包含整数 S。

接下来N行每行有一对整数,分别为 Ti 和 Ci,表示第 i 个任务单独完成所需的时间 Ti 及其费用系数 Ci。

输出格式
输出一个整数,表示最小总费用。

数据范围
1≤N≤5000,
0≤S≤50,
1≤Ti,Ci≤100
输入样例:
5
1
1 3
3 2
4 3
2 3
1 4
输出样例:
153
难度: 中等
时/空限制: 1s / 64MB
总通过数: 61
总尝试数: 83
来源: 《算法竞赛进阶指南》

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
#include<map>
#include<set>
#include<deque>
#include<queue>
#define ll long long
#define llu unsigned ll
using namespace std;
const int inf=0x3f3f3f3f;
const ll lnf=0x3f3f3f3f3f3f3f3f;
const int mod=1e9+7;
const int maxn=5100;
ll dp[maxn],sumt[maxn],sumc[maxn];
ll n,s;
int main(void)
{
    while(scanf("%lld%lld",&n,&s)!=EOF)
    {

        for(int i=1;i<=n;i++)
        {
            scanf("%lld%lld",&sumt[i],&sumc[i]);
            sumt[i]+=sumt[i-1];
            sumc[i]+=sumc[i-1];
        }
        memset(dp,0x3f,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]));
        }

        printf("%lld\n",dp[n]);
    }
    return 0;
}

二、任务安排2:
有 N 个任务排成一个序列在一台机器上等待执行,它们的顺序不得改变。

机器会把这 N 个任务分成若干批,每一批包含连续的若干个任务。

从时刻0开始,任务被分批加工,执行第 i 个任务所需的时间是 Ti。

另外,在每批任务开始前,机器需要 S 的启动时间,故执行一批任务所需的时间是启动时间 S 加上每个任务所需时间之和。

一个任务执行后,将在机器中稍作等待,直至该批任务全部执行完毕。

也就是说,同一批任务将在同一时刻完成。

每个任务的费用是它的完成时刻乘以一个费用系数 Ci。

请为机器规划一个分组方案,使得总费用最小。

输入格式
第一行包含整数 N。

第二行包含整数 S。

接下来N行每行有一对整数,分别为 Ti 和 Ci,表示第 i 个任务单独完成所需的时间 Ti 及其费用系数 Ci。

输出格式
输出一个整数,表示最小总费用。

数据范围
1≤N≤3∗105,
1≤S,Ti,Ci≤512
输入样例:
5
1
1 3
3 2
4 3
2 3
1 4
输出样例:
153

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
#include<map>
#include<set>
#include<deque>
#include<queue>
#define ll long long
#define llu unsigned ll
using namespace std;
const int inf=0x3f3f3f3f;
const ll lnf=0x3f3f3f3f3f3f3f3f;
const int mod=1e9+7;
const int maxn=305100;
ll dp[maxn],sumt[maxn],sumc[maxn];
ll n,s;
ll q[maxn];
int main(void)
{
    while(scanf("%lld%lld",&n,&s)!=EOF)
    {

        for(int i=1;i<=n;i++)
        {
            scanf("%lld%lld",&sumt[i],&sumc[i]);
            sumt[i]+=sumt[i-1];
            sumc[i]+=sumc[i-1];
        }
        memset(dp,0x3f,sizeof(dp));
        dp[0]=0;

        int l=1,r=1;
        q[1]=0;

        for(int i=1;i<=n;i++)
        {
            while(l<r&&(dp[q[l+1]]-dp[q[l]])<=(s+sumt[i])*(sumc[q[l+1]]-sumc[q[l]])) l++;

            dp[i]=dp[q[l]]-(s+sumt[i])*sumc[q[l]]+sumt[i]*sumc[i]+s*sumc[n];

            while(l<r&&(dp[q[r]]-dp[q[r-1]])*(sumc[i]-sumc[q[r]])>=(dp[i]-dp[q[r]])*(sumc[q[r]]-sumc[q[r-1]])) r--;

            q[++r]=i;
        }

        printf("%lld\n",dp[n]);
    }
    return 0;
}

三、任务安排3:

有 N 个任务排成一个序列在一台机器上等待执行,它们的顺序不得改变。

机器会把这 N 个任务分成若干批,每一批包含连续的若干个任务。

从时刻0开始,任务被分批加工,执行第 i 个任务所需的时间是 Ti。

另外,在每批任务开始前,机器需要 S 的启动时间,故执行一批任务所需的时间是启动时间 S 加上每个任务所需时间之和。

一个任务执行后,将在机器中稍作等待,直至该批任务全部执行完毕。

也就是说,同一批任务将在同一时刻完成。

每个任务的费用是它的完成时刻乘以一个费用系数 Ci。

请为机器规划一个分组方案,使得总费用最小。

输入格式
第一行包含整数 N。

第二行包含整数 S。

接下来N行每行有一对整数,分别为 Ti 和 Ci,表示第 i 个任务单独完成所需的时间 Ti 及其费用系数 Ci。

输出格式
输出一个整数,表示最小总费用。

数据范围
1≤N≤3∗105,
0≤S,Ci≤512,
−512≤Ti≤512
输入样例:
5
1
1 3
3 2
4 3
2 3
1 4
输出样例:
153

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
#include<map>
#include<set>
#include<deque>
#include<queue>
#define ll long long
#define llu unsigned ll
using namespace std;
const int inf=0x3f3f3f3f;
const ll lnf=0x3f3f3f3f3f3f3f3f;
const int mod=1e9+7;
const int maxn=305100;
ll dp[maxn],sumt[maxn],sumc[maxn];
ll n,s;
ll q[maxn];

int my_search(int l,int r,int k)
{
    if(l==r) return q[l];
    while(l<r)
    {
        int mid=(l+r)>>1;
        if(dp[q[mid+1]]-dp[q[mid]]<=k*(sumc[q[mid+1]]-sumc[q[mid]])) l=mid+1;
        else r=mid;
    }
    return q[l];
}

int main(void)
{
    while(scanf("%lld%lld",&n,&s)!=EOF)
    {

        for(int i=1;i<=n;i++)
        {
            scanf("%lld%lld",&sumt[i],&sumc[i]);
            sumt[i]+=sumt[i-1];
            sumc[i]+=sumc[i-1];
        }
        memset(dp,0x3f,sizeof(dp));
        dp[0]=0;

        int l=1,r=1;
        q[1]=0;

        for(int i=1;i<=n;i++)
        {
            int p=my_search(l,r,s+sumt[i]);

            dp[i]=dp[p]-(s+sumt[i])*sumc[p]+sumt[i]*sumc[i]+s*sumc[n];

            while(l<r&&(dp[q[r]]-dp[q[r-1]])*(sumc[i]-sumc[q[r]])>=(dp[i]-dp[q[r]])*(sumc[q[r]]-sumc[q[r-1]])) r--;

            q[++r]=i;
        }

        printf("%lld\n",dp[n]);
    }
    return 0;
}

四、运输小猫:

小S是农场主,他养了 M 只猫,雇了 P 位饲养员。

农场中有一条笔直的路,路边有 N 座山,从 1 到 N 编号。

第 i 座山与第 i-1 座山之间的距离为 Di。

饲养员都住在 1 号山。

有一天,猫出去玩。

第 i 只猫去 Hi 号山玩,玩到时刻 Ti 停止,然后在原地等饲养员来接。

饲养员们必须回收所有的猫。

每个饲养员沿着路从 1 号山走到 N 号山,把各座山上已经在等待的猫全部接走。

饲养员在路上行走需要时间,速度为1 米/单位时间。

饲养员在每座山上接猫的时间可以忽略,可以携带的猫的数量为无穷大。

例如有两座相距为 1 的山,一只猫在 2 号山玩,玩到时刻 3 开始等待。

如果饲养员从 1 号山在时刻 2 或 3 出发,那么他可以接到猫,猫的等待时间为 0 或 1。

而如果他于时刻 1 出发,那么他将于时刻 2 经过 2 号山,不能接到当时仍在玩的猫。

你的任务是规划每个饲养员从 1 号山出发的时间,使得所有猫等待时间的总和尽量小。

饲养员出发的时间可以为负。

输入格式
第一行包含三个整数N,M,P。

第二行包含n-1个整数,D2,D3,…,DN。

接下来M行,每行包含两个整数Hi和Ti。

输出格式
输出一个整数,表示所有猫等待时间的总和的最小值。

数据范围
2≤N≤105,
1≤M≤105,
1≤P≤100,
1≤Di<1000,
1≤Hi≤N,
0≤Ti≤109
输入样例:
4 6 2
1 3 5
1 0
2 1
4 9
1 10
2 10
3 12
输出样例:
3

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
#include<map>
#include<set>
#include<deque>
#include<queue>
#define ll long long
#define llu unsigned ll
using namespace std;
const int inf=0x3f3f3f3f;
const ll lnf=0x3f3f3f3f3f3f3f3f;
const int mod=1e9+7;
const int maxn=100100;
ll f[110][maxn],s[maxn],a[maxn],d[maxn],q[maxn];
ll g[maxn];
int n,m,p;
int main(void)
{
    while(scanf("%d%d%d",&n,&m,&p)!=EOF)
    {
        d[1]=0;
        for(int i=2;i<=n;i++)
        {
            scanf("%lld",&d[i]);
            d[i]+=d[i-1];
        }
        ll hi,ti;
        for(int i=1;i<=m;i++)
        {
            scanf("%lld%lld",&hi,&ti);
            a[i]=ti-d[hi];
        }
        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++)
        {
            for(int j=1;j<=m;j++) g[j]=f[i-1][j]+s[j];

            int l=1,r=1;
            q[1]=0;

            for(int j=1;j<=m;j++)
            {
                while(l<r&&g[q[l+1]]-g[q[l]]<=a[j]*(q[l+1]-q[l])) l++;

                f[i][j]=min(min(f[i][j],f[i-1][j]),g[q[l]]+a[j]*(j-q[l])-s[j]);

                if(f[i-1][j]==lnf) continue;

                while(l<r&&(g[j]-g[q[r]])*(q[r]-q[r-1])<=(g[q[r]]-g[q[r-1]])*(j-q[r])) r--;
                q[++r]=j;
            }



        }

        printf("%lld\n",f[p][m]);
    }
    return 0;
}

斜率优化技巧性比较强,思想深邃而巧妙,感觉如果能灵活掌握,对解题会大有益处。

;