任务安排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]); }