CH5A01&BZoj2726 任务安排(DP)(斜率优化)

版权声明:本文为博主原创文章,未经博主允许不得转载,除非先点了赞。 https://blog.csdn.net/A_Bright_CH/article/details/82837717

例题

CH5A01 任务安排1

题解1

DP

设f[i][j]表示把前i个机器分成j批修理的最小费用,St,Sc分别对应数组t和c的前缀和。
容易写出一个O(n^3)的DP方程:

f[i][j]=\min_{0\leqslant k<i }\left\{f[k][j-1]+(St[i]+S*j)*(Sc[i]-Sc[k])\right\}

但是这个方法显然不够优秀。我们可以考虑去掉分成j批这一维,并用“费用提前计算”的思想来维护新的f。
方程如下:

f[i]=\min_{1\leqslant j<i} \left\{ f[j]+S*(Sc[N]-Sc[j])+St[i]*(Sc[i]-Sc[j])\right\}

题解2

DP+斜率优化
我们把i看做定量,j看做变量,然后把所有仅与j有关的(决策变量)移到等号左边,仅与i有关的或者与i和j都有关的(其它变量)移到等号右边。去掉min函数,可以得到这样一个式子:

f[j]=(S+St[i])*Sc[j]+F[i]-St[i]*Sc[i]-S*Sc[N]

我们可以理解成在一个以Sc[j]为横坐标,f[j]为纵坐标的坐标系上,有一条斜率为(S+St[i])的直线,截距为F[i]-St[i]*Sc[i]-S*Sc[N]。决策点坐标为(Sc[j],f[j])。
要使截距最小,应当维护下凸壳。

这样理解的关键在于选取了什么作为斜率,其实只要是两式相乘的结构就可以,但一定要能选变量作为横坐标。并把仅与决策变量相关的放在一边,其余的放在一边,意思是一旦枚举了一个决策变量,整个解析式的一边就确定了。另外,如果问题直线的斜率能呈递增\减,决策点的横坐标能呈递增\减,问题就会更简单了。

扫描二维码关注公众号,回复: 3482820 查看本文章

拓展

(1)如果问题直线的斜率不单调怎么办?(例题:BZoj2726 [SDOI2012]任务安排)
答:应当把所有合法决策点都放入队列,决策点间的斜率维护一个凸壳。对于每个问题斜率k,在这个凸壳中二分,找到一个决策点,它与左右两点的斜率分别大于、小于k,这个点就是k的最优点。

(2)如果决策点的横坐标不单调怎么办?
答:对方程变形,使得横坐标单调,倒序DP,再用(1)的方法解决。

(3)如果问题直线的斜率和决策点的横坐标都不单调怎么办?
答:既然如此,说明要在凸壳上动态插点、动态查询,要用平衡树来维护凸壳。

代码

CH5A01 任务安排1:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=5010;

int n,s;
int f[maxn];
int t[maxn],c[maxn];
int st[maxn],sc[maxn];

int q[maxn];int l,r;

double calc(int x,int y)
{
	return (double)(f[x]-f[y])/(sc[x]-sc[y]);
}

int main()
{
	scanf("%d%d",&n,&s);
	for(int i=1;i<=n;i++)
	{
		scanf("%d%d",&t[i],&c[i]);
		st[i]=st[i-1]+t[i];sc[i]=sc[i-1]+c[i];
	}
	l=r=1;q[1]=0;
	f[0]=0;
	for(int i=1;i<=n;i++)
	{
		while(l<r && calc(q[l],q[l+1])<=s+st[i]) l++;//debug l<=r
		int j=q[l];
		f[i]=-(s+st[i])*sc[j]+f[j]+st[i]*sc[i]+s*sc[n];
		while(l<r && calc(q[r-1],q[r])>calc(q[r],i)) r--;
		q[++r]=i;
	}
	printf("%d\n",f[n]);
	return 0;
}

BZoj2726:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxn=300010;
 
int n,s;
int t[maxn],c[maxn];
ll st[maxn],sc[maxn];
 
ll f[maxn];
int q[maxn];
 
double calc(int x,int y)
{
    return (double)(f[x]-f[y])/(sc[x]-sc[y]);
}
 
int erfen(int l,int r,int c)//在[l,r]中找c的后继 
{
    while(l<r)
    {
        int mid=l+r>>1;
        if(f[q[mid+1]]-f[q[mid]]>c*(sc[q[mid+1]]-sc[q[mid]]))
        {
            r=mid;
        }
        else l=mid+1;
    }
    return q[l];
}
 
int main()
{
    scanf("%d%d",&n,&s);
    for(int i=1;i<=n;i++)
    {
        scanf("%d%d",&t[i],&c[i]);
        st[i]=st[i-1]+t[i];sc[i]=sc[i-1]+c[i];
    }
    int l,r;
    l=r=1;q[1]=0;
    f[0]=0;
    for(int i=1;i<=n;i++)
    {
        int j=q[l];
        if(l<r) j=erfen(l,r,s+st[i]);
        f[i]=f[j]-(s+st[i])*sc[j]+st[i]*sc[i]+s*sc[n];
        while(l<r && (f[q[r-1]]-f[q[r]]) * (sc[q[r]]-sc[i]) >= (f[q[r]]-f[i]) * (sc[q[r-1]]-sc[q[r]]) ) r--;
        q[++r]=i;
    }
    printf("%lld\n",f[n]);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/A_Bright_CH/article/details/82837717