洛谷 P2365 任务安排

这道题有点折磨我。。

题目链接

题目描述

n n n 个任务排成一个序列在一台机器上等待完成(顺序不得改变),这 n n n 个任务被分成若干批,每批包含相邻的若干任务。

从零时刻开始,这些任务被分批加工,第 i i i 个任务单独完成所需的时间为 t i t_i ti。在每批任务开始前,机器需要启动时间 s s s,而完成这批任务所需的时间是各个任务需要时间的总和(同一批任务将在同一时刻完成)。

每个任务的费用是它的完成时刻乘以一个费用系数 f i f_i fi。请确定一个分组方案,使得总费用最小。

详情看题目主页吧

【数据范围】
对于 100 % 100\% 100% 的数据, 1 ≤ n ≤ 5000 1\le n \le 5000 1n5000 0 ≤ s ≤ 50 0 \le s \le 50 0s50 1 ≤ t i , f i ≤ 100 1\le t_i,f_i \le 100 1ti,fi100

Attention

要过substack2的数据要用long long (网上很多的答案都是几年前的了,都是intAC了)


方法

动态规划 + 费用提前
DP转移方程
d p [ i ] = min ⁡ { d p [ i ] , d p [ j ] + s u m T [ i ] ∗ ( s u m F ( i ) − s u m F ( j ) ) + s ∗ ( s u m F ( n ) − s u m F ( j ) ) } dp[i] = \min{\{dp[i],\quad dp[j] + sum_T[i]*(sum_F(i) - sum_F(j)) + s*(sum_F(n) - sum_F(j))\}} dp[i]=min{dp[i],dp[j]+sumT[i](sumF(i)sumF(j))+s(sumF(n)sumF(j))}

  • d p [ i ] dp[i] dp[i]表示前 i i i 个任务加工的费用与机器延误费用的和的最优值

    机器延误费用 = s ∗ ( s u m F ( n ) − s u m F ( j ) s*(sum_F(n) - sum_F(j) s(sumF(n)sumF(j)

  • s u m T sum_T sumT是时间前缀和 , s u m F sum_F sumF是费用率前缀和

    s u m T [ i ] sum_T[i] sumT[i]相当于执行第i个任务的全任务时间(全时间=任务时间+启动时间

  • j j j 表示前 i i i 个任务中的最后第二组任务的最后一个

    事实上我们是不能直接确定 j j j的位置的(thinking


斜率优化wiki–直达

理解

与一般的 O ( n 3 ) O(n^3) O(n3) DP 相比,通过费用提前避免了确定分组的个数的步骤降低了时间复杂度。
但相应的为了计算由分组产生的费用(机器启动耗时导致的),我们必须寻找一个有效的计算方法——费用提前
在计算过程中我们把由于机器启动附加时间产生的费用,加到了此时的dp[i]中。换而言之,此时的 d p [ i ] dp[i] dp[i]存储了未来的信息。

避免了在普通动态规划过程中无法计算由于前面机器启动,后面时间增加而导致的 k k < i k_{k<i} kk<i任务上涨的费用的问题

代码

Alert:注意前缀和,与 d p dp dp数组的索引

#include<iostream>
#include<stdio.h>
#include<string>
#include <cstring>
using namespace std;

long long sumt[5001], sumf[5000], dp[5001],n, s;
//long long t[5001], f[5001];
int main() {
	cin >> n >> s;
	
	for (long long i = 1; i <= n; ++i) {
		long long temp1, temp2;
		cin >> temp1 >> temp2;
		sumt[i] = sumt[i - 1] + temp1;
		sumf[i] = sumf[i - 1] + temp2;
		
	}
	memset(dp, -1, sizeof(dp));
	// 数组从1开始
	dp[0] = 0;
	
	// 生成dp[i]
	for (long long i = 1; i <= n; ++i) {	
	// 任务 i 要么加入上一组,要么新成一组 前提是dp[i]必须正确 所以必须是两层
	// 第一层确定dp[i],第二层进行计算
	// 应该是可以优化的,但没有解决方法,(O(n) 参考斜率/凸优化) 		
		for (long long j = 0; j < i; j++) {
		// j 遍历是为了找到上一组的机器启动位置(具有数值上的特殊性)
			long long temp = dp[j] + sumt[i] * (sumf[i] - sumf[j]) + s * (sumf[n] - sumf[j]);
			if (dp[i] == -1 || temp < dp[i])
				dp[i] = temp;
		}
	}
	cout << dp[n];
	return 0;
}

END

最后推荐一种倒序DP的方法 link
简单易懂, ╭(●`∀′●)╯。

  • 31
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值