yzy讲的斜率优化嘤!!!
yzy学长太棒啦!!!
要注意维护的到底是下凸壳还是上凸壳取决于斜率的正负和需要最大化还是最小化截距,计算的时候还要注意x和y的正负
下面lyd的书上同一道题不同的数据范围或条件给了不同的做法
1、luogu2356任务安排
题目描述
N个任务排成一个序列在一台机器上等待完成(顺序不得改变),这N个任务被分成若干批,每批包含相邻的若干任务。从时刻0开始,这些任务被分批加工,第i个任务单独完成所需的时间是Ti。在每批任务开始前,机器需要启动时间S,而完成这批任务所需的时间是各个任务需要时间的总和(同一批任务将在同一时刻完成)。每个任务的费用是它的完成时刻乘以一个费用系数Fi。请确定一个分组方案,使得总费用最小。
例如:S=1;T={1,3,4,2,1};F={3,2,3,3,4}。如果分组方案是{1,2}、{3}、{4,5},则完成时间分别为{5,5,10,14,14},费用C={15,10,30,42,56},总费用就是153。
输入输出格式
输入格式:
第一行是N(1<=N<=5000)。
第二行是S(0<=S<=50)。
下面N行每行有一对数,分别为Ti和Fi,均为不大于100的正整数,表示第i个任务单独完成所需的时间是Ti及其费用系数Fi。
输出格式:
一个数,最小的总费用。
输入输出样例
输入样例#1:
5
1
1 3
3 2
4 3
2 3
1 4
输出样例#1:
153
这个题还用不着斜率优化,有一个优化小技巧
就是一种名为“费用提前算”的经典思想
也就是在一批任务开始对后续任务产生影响时,就先把费用累加到答案中
成功的把n^3复杂度优化到了n^2
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define maxn 5005
#define LL long long
using namespace std;
int n,s,t[maxn],c[maxn];
LL sumt[maxn],sumc[maxn],f[maxn];
inline int rd(){
int x=0,f=1;char c=' ';
while(c<'0' || c>'9') {if(c=='-') f=-1;c=getchar();}
while(c<='9' && c>='0') x=x*10+c-'0',c=getchar();
return x*f;
}
int main(){
n=rd(); s=rd();
for(int i=1;i<=n;i++){
t[i]=rd(); sumt[i]=sumt[i-1]+t[i];
c[i]=rd(); sumc[i]=sumc[i-1]+c[i];
}
memset(f,0x3f,sizeof f);
f[0]=0;
for(int i=1;i<=n;i++)
for(int j=0;j<i;j++){
f[i]=min(f[j]+sumt[i]*(sumc[i]-sumc[j])+1LL*s*(sumc[n]-sumc[j]),f[i]);
}
printf("%lld\n",f[n]);
return 0;
}
2、poj1180
题目一样,数据范围n改到了10000
就要用斜率优化啦!!!
每次从队头开始,看l和l+1的斜率,如果<=当前斜率
出队,知道l=r或者>为止
此时队头就是更新f[i]的答案
而更新完f[i]要把i加入队尾,此时维护一个下凸壳
看r和r-1的斜率是不是比i和r要小
如果不是就把队尾减掉,再把i加入
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#define maxn 10005
#define LL long long
using namespace std;
int n,s,q[maxn];
LL sumt[maxn],sumc[maxn],f[maxn];
inline int rd(){
int x=0,f=1;char c=' ';
while(c<'0' || c>'9') {if(c=='-') f=-1;c=getchar();}
while(c<='9' && c>='0') x=x*10+c-'0',c=getchar();
return x*f;
}
int main(){
n=rd(); s=rd();
for(int i=1;i<=n;i++){
int t=rd(),c=rd();
sumt[i]=sumt[i-1]+t; sumc[i]=sumc[i-1]+c;
}
memset(f,0x3f,sizeof f); f[0]=0;
int l=1,r=1;
q[1]=0;
for(int i=1;i<=n;i++){
while(l<r && (f[q[l+1]]-f[q[l]])<=(s+sumt[i])*(sumc[q[l+1]]-sumc[q[l]])) l++;
f[i]=f[q[l]]-(s+sumt[i])*sumc[q[l]]+sumt[i]*sumc[i]+s*sumc[n];
while(l<r && (f[q[r]]-f[q[r-1]])*(sumc[i]-sumc[q[r]])>=(f[i]-f[q[r]])*(sumc[q[r]]-sumc[q[r-1]]))
r--;
q[++r]=i;
}
printf("%lld\n",f[n]);
return 0;
}
3、bzoj2726
题面一样,数据范围n<=300000
同时执行任务的时间T可能是负数
意味着sumt不再有单调性,此时在队列二分找到可更新的
加入队尾时还按照原来的方法
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#define maxn 300005
#define LL long long
using namespace std;
int n,s;
LL sumc[maxn],sumt[maxn],f[maxn],q[maxn],l,r;
inline int rd(){
int x=0,f=1;char c=' ';
while(c<'0' || c>'9') {if(c=='-') f=-1;c=getchar();}
while(c<='9' && c>='0') x=x*10+c-'0',c=getchar();
return x*f;
}
int search(int i,int k){
if(l==r) return q[l];
int L=l,R=r;
while(L<R){
int mid=(L+R)>>1;
if(f[q[mid+1]]-f[q[mid]]<=k*(sumc[q[mid+1]]-sumc[q[mid]]))
L=mid+1;
else R=mid;
}
return q[L];
}
int main(){
n=rd(); s=rd();
for(int i=1;i<=n;i++){
int t=rd(),c=rd();
sumt[i]=sumt[i-1]+t; sumc[i]=sumc[i-1]+c;
}
l=1,r=1; q[1]=0;
for(int i=1;i<=n;i++){
int p=search(i,s+sumt[i]);
f[i]=f[p]-(s+sumt[i])*sumc[p]+sumt[i]*sumc[i]+s*sumc[n];
while(l<r && (f[q[r]]-f[q[r-1]])*(sumc[i]-sumc[q[r]])>=(f[i]-f[q[r]])*(sumc[q[r]]-sumc[q[r-1]]))
r--;
q[++r]=i;
}
printf("%lld\n",f[n]);
return 0;
}
4、思考:
如果ti是正整数,ci可能为负数怎么办?
可以倒序dp,搞一个让sumt是横坐标的转移方程,sumc是斜率中的一项,也可以用二分法,高端操作
若二者都可能为负数,意味着要在凸壳任意位置动态插入定点、动态查询,此时用平衡树来维护凸壳(其实我平衡树还没写过题)