任务安排1
https://loj.ac/problem/10184
题目
有 $N$ 个任务排成一个序列在一台机器上等待执行,它们的顺序不得改变。机器会把这 $N$ 个任务分成若干批,每一批包含连续的若干个任务。从时刻 $0$ 开始,任务被分批加工,执行第i个任务所需的时间是 $T_i$。另外,在每批任务开始前,机器需要 $S$ 的启动时间,故执行一批任务所需的时间是启动时间 $S$ 加上每个任务所需时间之和。
一个任务执行后,将在机器中稍作等待,直至该批任务全部执行完毕。也就是说,同一批任务将在同一时刻完成。每个任务的费用是它的完成时刻乘以一个费用系数 $C_i$。
请为机器规划一个分组方案,使得总费用最小。
$1leqslant Nleqslant 5000,0leqslant Sleqslant 50,1le T_i,C_ileqslant 100$
题解
提前计算启动时间
$dp[i]=min{dp[j]+S*(SC[N]-SC[j])+ST[i]*(SC[i]-SC[j])}$
时间复杂度$mathcal{O}(n^2)$
AC代码
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#include<set>
#define REP(i,a,b) for(register int i=(a); i<(b); i++)
#define REPE(i,a,b) for(register int i=(a); i<=(b); i++)
#define PERE(i,a,b) for(register int i=(a); i>=(b); i--)
using namespace std;
typedef long long ll;
#define MAXN 5007
ll dp[MAXN];
int sa[MAXN], sc[MAXN];
int main() {
int n,s;
scanf("%d%d", &n, &s);
REPE(i,1,n) scanf("%d%d", &sa[i], &sc[i]);
sa[0]=sc[0]=0;
REPE(i,2,n) sa[i]+=sa[i-1];
PERE(i,n-1,1) sc[i]+=sc[i+1];
memset(dp,0x3f,sizeof(dp));
dp[0]=0; sc[n+1]=0;
REPE(i,1,n) {
REP(k,0,i) {
dp[i]=min(dp[i],dp[k]+(ll)sa[i]*(sc[k+1]-sc[i+1])+s*sc[k+1]);
}
}
printf("%lld
", dp[n]);
}
任务安排2
https://loj.ac/problem/10185
题目
$1le Nle 10^4,0le Sle 50,1le T_i,C_ile 100$
老题目,数据范围在当时不能过$mathcal{O}(n^2)$
题解
$dp[i]=min{dp[j]+S*(SC[N]-SC[j])+ST[i]*(SC[i]-SC[j])}$
把式子中的含i项看作常数,去掉$min$,分离得到所有决策的表达式
$dp[j]=SC[j]cdot(S+SC[i])-Scdot SC[N]-ST[i]cdot SC[i]+dp[i]$
将dp[j]和SC[j]看作变量,那么就得到了一条直线的表达式,斜率是$S+SC[i]$,截距是$-Scdot SC[N]-ST[i]cdot SC[i]+dp[i]$,由于$-Scdot SC[N]-ST[i]cdot SC[i]$是常数,所以截距越小,$dp[i]$就越小
所以只需要代入$(SC[j],dp[j])$进行计算就可以了,时间复杂度仍然是$mathcal{O}(n^2)$
利用线性规划,可以发现结果只可能在$(SC[j],dp[j])$下凸包上取,而且是斜率$geqslant (S+SC[i])$的线段左端点处
并且$SC[j]$是单调递增的,所以可以维护凸包,然后二分,时间复杂度$mathcal{O}(nlog n)$
但是斜率也是单调递增的,所以凸包的左边部分可以去掉,时间复杂度$mathcal{O}(n)$
AC代码
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#include<set>
#define REP(i,a,b) for(register int i=(a); i<(b); i++)
#define REPE(i,a,b) for(register int i=(a); i<=(b); i++)
#define PERE(i,a,b) for(register int i=(a); i>=(b); i--)
using namespace std;
typedef long long ll;
#define MAXN 10007
int S, SA[MAXN], SC[MAXN];
ll dp[MAXN];
int N;
int q[MAXN];
int main() {
scanf("%d%d", &N, &S);
SA[0]=SC[0]=0;
REPE(i,1,N) {
scanf("%d%d", &SA[i], &SC[i]);
SA[i]+=SA[i-1], SC[i]+=SC[i-1];
}
int l=0,r=0;
dp[0]=0;
q[r++]=0;
REPE(i,1,N) {
while(r-l>1 && (dp[q[l+1]]-dp[q[l]])<=(S+SA[i])*ll(SC[q[l+1]]-SC[q[l]])) l++;
int j=q[l];
dp[i]=dp[j]+S*ll(SC[N]-SC[j])+SA[i]*ll(SC[i]-SC[j]);
while(r-l>1 && (dp[i]-dp[q[r-1]])*(SC[q[r-1]]-SC[q[r-2]])<=(dp[q[r-1]]-dp[q[r-2]])*(SC[i]-SC[q[r-1]])) r--;
q[r++]=i;
}
printf("%lld
", dp[N]);
}
任务安排3
https://loj.ac/problem/10186
题目
$1le Nle 3 imes 10^5,1le Sle 2^8,|T_i|le 2^8,0le C_ile 2^8$
题解
和2一样,只是斜率不一定单调递增,所以只能用二分,时间复杂度$mathcal{O}(nlog n)$
AC代码
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#include<set>
#define REP(i,a,b) for(register int i=(a); i<(b); i++)
#define REPE(i,a,b) for(register int i=(a); i<=(b); i++)
#define PERE(i,a,b) for(register int i=(a); i>=(b); i--)
using namespace std;
typedef long long ll;
#define MAXN 300007
int S, SA[MAXN], SC[MAXN];
ll dp[MAXN];
int N;
int q[MAXN];
inline int bs(int l, int r, int i) {
ll k=S+SA[i];
r--;
while(l<r) {
int m=(l+r)>>1;
if((dp[q[m+1]]-dp[q[m]])<=k*(SC[q[m+1]]-SC[q[m]])) l=m+1;
else r=m;
}
return q[l];
}
int main() {
scanf("%d%d", &N, &S);
SA[0]=SC[0]=0;
REPE(i,1,N) {
scanf("%d%d", &SA[i], &SC[i]);
SA[i]+=SA[i-1], SC[i]+=SC[i-1];
}
int l=0,r=0;
dp[0]=0;
q[r++]=0;
REPE(i,1,N) {
int j=bs(l,r,i);
dp[i]=dp[j]+S*ll(SC[N]-SC[j])+SA[i]*ll(SC[i]-SC[j]);
while(r-l>1 && (dp[i]-dp[q[r-1]])*(SC[q[r-1]]-SC[q[r-2]])<=(dp[q[r-1]]-dp[q[r-2]])*(SC[i]-SC[q[r-1]])) r--;
q[r++]=i;
}
printf("%lld
", dp[N]);
}