任务安排(SDOI2012)斜率优化进阶

本文深入探讨了一种利用动态规划和单调队列优化技术来解决具有复杂约束条件的调度问题的方法。通过巧妙地定义状态转移方程和维护一个单调队列,作者成功地在大规模数据集上实现了高效的解决方案。文章详细解释了状态压缩动态规划的原理、关键步骤以及如何通过优化避免冗余计算,最终展示了该方法在实际应用中的高效性和实用性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目描述

N N N个任务排成一个序列在一台机器上等待完成(顺序不得改变),这 N N N个任务被分成若干批,每批包含相邻的若干任务。从时刻 0 0 0开始,这些任务被分批加工,第 i i i个任务单独完成所需的时间是 T i T_i Ti。在每批任务开始前,机器需要启动时间S,而完成这批任务所需的时间是各个任务需要时间的总和(同一批任务将在同一时刻完成)。每个任务的费用是它的完成时刻乘以一个费用系数 C i C_i Ci
请确定一个分组方案,使得总费用最小。


数据规模

1 ≤ N ≤ 3 ∗ 1 0 5 0 ≤ S , C ≤ 512 − 512 ≤ T ≤ 512 1≤N≤3*10^5 0≤S,C≤512 -512≤T≤512 1N31050S,C512512T512


思路

首先,这还是个很明显的划分类 D P DP DP
直接上状态: f [ i ] [ j ] f[i][j] f[i][j]表示前i个任务分成j批的最小代价。
易得 f [ i ] [ j ] = m i n f [ k ] [ j − 1 ] + ( j ∗ s + s u m T [ i ] ) ∗ ( s u m C [ i ] − s u m C [ k ] ) f[i][j] = min{f[k][j - 1] + (j * s + sum_T[i]) * (sum_C[i] - sum_C[k])} f[i][j]=minf[k][j1]+(js+sumT[i])(sumC[i]sumC[k])
但是这个状态已经是 O ( n 2 ) O(n^2) O(n2)级别的了
/-----------华---------丽-----------丽---------的-----------分---------割---------线-----------/
然后,我们会发现限制状态的最大因素就是 j j j
整个转移中唯一与j有关的就是 “ j ∗ s ” “j * s” js
因此,我们是否可以想办法将它去掉呢?
考虑在第i个任务结束后重新分一组,那么对第 i + 1 i + 1 i+1 ~ n n n 个任务的影响就是加上了 ( s u m C [ n ] − s u m C [ i ] ) ∗ s (sum_C[n] - sum_C[i]) * s (sumC[n]sumC[i])s
那么,我们就可以将j这维状态直接去掉
状态就变成了: f [ i ] f[i] f[i]表示前i个任务的最小代价
转移: f [ i ] = m i n f [ i ] , f [ j ] + ( s u m C [ i ] − s u m C [ j ] ) ∗ ( s + s u m T [ i ] ) + ( s u m C [ n ] − s u m C [ i ] ) ∗ s f[i] = min{f[i] , f[j] + (sum_C[i] - sum_C[j]) * (s + sum_T[i])} + (sum_C[n] - sum_C[i]) * s f[i]=minf[i],f[j]+(sumC[i]sumC[j])(s+sumT[i])+(sumC[n]sumC[i])s


-----------华---------丽-----------丽---------的-----------分---------割---------线-----------


然后,我们又发现,这是个非常经典的 1 D / 1 D 1D/1D 1D/1D动态规划
显然是无法通过 30 W 30W 30W级别的数据。
考虑优化,对于一道一维状态的 D P DP DP题,优化方向依然只有一个——转移。
(否则把状态优化了不就成了贪心吗?)
如何减少重复或不必要的枚举呢?
1 ≤ j 1 &lt; j 2 &lt; i 1 ≤ j1 &lt; j2 &lt; i 1j1<j2<i
则对于 i i i的决策, j 2 j_2 j2 j 1 j_1 j1优等价于满足
f [ j 2 ] + ( s u m C [ i ] − s u m C [ j 2 ] ) ∗ ( s + s u m T [ i ] ) &lt; f[j_2] + (sum_C[i] - sum_C[j_2]) * (s + sum_T[i]) &lt; f[j2]+(sumC[i]sumC[j2])(s+sumT[i])<
f [ j 1 ] + ( s u m C [ i ] − s u m C [ j 1 ] ) ∗ ( s + s u m T [ i ] ) f[j_1] + (sum_C[i] - sum_C[j_1]) * (s + sum_T[i]) f[j1]+(sumC[i]sumC[j1])(s+sumT[i])
简单的打开并整理过后就得到了
( f [ j 2 ] − f [ j 1 ] ) − s ∗ ( s u m C [ j 2 ] − s u m C [ j 1 ] ) (f[j_2]-f[j_1]) - s * (sum_C[j_2] - sum_C[j_1]) (f[j2]f[j1])s(sumC[j2]sumC[j1])
------------------------------------------------------------------------ &lt; s u m T [ i ] &lt; sum_T[i] <sumT[i]
s u m C [ j 2 ] − s u m C [ j 1 ] sum_C[j_2] - sum_C[j_1] sumC[j2]sumC[j1]


但这道题与 h d u 3057 hdu3057 hdu3057不同之处便是 s u m T [ i ] sum_T[i] sumT[i]不单调了
但是,仔细思考,这并没有破坏队尾维护的性质,即我们依然可以维护一个单调队列(实则是一个单调栈)
而区别就是现在取值的时候,要在这个单调序列中二分出一个斜率与 s u m T [ i ] sum_T[i] sumT[i]最为接近的值


T i p s Tips Tips:写读优的同志们千万别忘了:此题有负数!此题有负数!此题有负数!本人就 W A WA WA了半个小时 Q A Q QAQ QAQ
注:对斜率优化基础还不理解的可以参考斜率优化基础


代码

#include<cstdio>
using namespace std;
struct node
{
	long long t , c;
}
sum[300005];
long long f[300005];
int q[300005];
long long s;
inline int read()
{
	char ch = getchar();
	int flag = 1;
	while(ch < '0' || ch > '9')
	{
		if(ch == '-') flag = -1;
		ch = getchar();
	}
	int x = 0;
	while(ch >= '0' && ch <= '9') x = x * 10 + ch - 48 , ch = getchar();
	return x * flag;
}
inline bool cmp1(int j2 , int j1 , long long k)
{
	return f[j2] - f[j1] - s * (sum[j2].c - sum[j1].c) <= k * (sum[j2].c - sum[j1].c);
}
inline bool cmp2(int j3 , int j2 , int j1)
{
	return (f[j3] - f[j2] - s * (sum[j3].c - sum[j2].c)) * (sum[j2].c - sum[j1].c) <= (f[j2] - f[j1] - s * (sum[j2].c - sum[j1].c)) * (sum[j3].c - sum[j2].c);
}
inline int find(int l , int r , long long key)
{
	while(l < r)
	{
		int mid = (l + r) >> 1;
		if(cmp1(q[mid + 1] , q[mid] , key)) l = mid + 1;
		else r = mid;
	}
	return q[r];
}
int main()
{
	int n = read();
	s = read();
	for(int i = 1;i <= n;i++)
	{
		int t = read() , c = read();
		sum[i].t = sum[i - 1].t + t;
		sum[i].c = sum[i - 1].c + c;
	}
	int head = 1 , tail = 1;
	q[1] = 0;
	f[0] = 0;
	for(int i = 1;i <= n;i++)
	{
		int loc = find(head , tail , sum[i].t);
		f[i] = f[loc] + (s + sum[i].t) * (sum[i].c - sum[loc].c) + (sum[n].c - sum[i].c) * s;
		while(head < tail && cmp2(i , q[tail] , q[tail - 1])) tail--;
		q[++tail] = i;
	}
	printf("%lld\n",f[n]);
	return 0;
}
内容概要:该PPT详细介绍了企业架构设计的方法论,涵盖业务架构、数据架构、应用架构和技术架构四大核心模块。首先分析了企业架构现状,包括业务、数据、应用和技术四大架构的内容和关系,明确了企业架构设计的重要性。接着,阐述了新版企业架构总体框架(CSG-EAF 2.0)的形成过程,强调其融合了传统架构设计(TOGAF)和领域驱动设计(DDD)的优势,以适应数字化转型需求。业务架构部分通过梳理企业级和专业级价值流,细化业务能力、流程和对象,确保业务战略的有效落地。数据架构部分则遵循五大原则,确保数据的准确、一致和高效使用。应用架构方面,提出了分层解耦和服务化的设计原则,以提高灵活性和响应速度。最后,技术架构部分围绕技术框架、组件、平台和部署节点进行了详细设计,确保技术架构的稳定性和扩展性。 适合人群:适用于具有一定企业架构设计经验的IT架构师、项目经理和业务分析师,特别是那些希望深入了解如何将企业架构设计与数字化转型相结合的专业人士。 使用场景及目标:①帮助企业和组织梳理业务流程,优化业务能力,实现战略目标;②指导数据管理和应用开发,确保数据的一致性和应用的高效性;③为技术选型和系统部署提供科学依据,确保技术架构的稳定性和扩展性。 阅读建议:此资源内容详尽,涵盖企业架构设计的各个方面。建议读者在学习过程中,结合实际案例进行理解和实践,重点关注各架构模块之间的关联和协同,以便更好地应用于实际工作中。
资 源 简 介 独立分量分析(Independent Component Analysis,简称ICA)是近二十年来逐渐发展起来的一种盲信号分离方法。它是一种统计方法,其目的是从由传感器收集到的混合信号中分离相互独立的源信号,使得这些分离出来的源信号之间尽可能独立。它在语音识别、电信和医学信号处理等信号处理方面有着广泛的应用,目前已成为盲信号处理,人工神经网络等研究领域中的一个研究热点。本文简要的阐述了ICA的发展、应用和现状,详细地论述了ICA的原理及实现过程,系统地介绍了目前几种主要ICA算法以及它们之间的内在联系, 详 情 说 明 独立分量分析(Independent Component Analysis,简称ICA)是近二十年来逐渐发展起来的一种盲信号分离方法。它是一种统计方法,其目的是从由传感器收集到的混合信号中分离相互独立的源信号,使得这些分离出来的源信号之间尽可能独立。它在语音识别、电信和医学信号处理等信号处理方面有着广泛的应用,目前已成为盲信号处理,人工神经网络等研究领域中的一个研究热点。 本文简要的阐述了ICA的发展、应用和现状,详细地论述了ICA的原理及实现过程,系统地介绍了目前几种主要ICA算法以及它们之间的内在联系,在此基础上重点分析了一种快速ICA实现算法一FastICA。物质的非线性荧光谱信号可以看成是由多个相互独立的源信号组合成的混合信号,而这些独立的源信号可以看成是光谱的特征信号。为了更好的了解光谱信号的特征,本文利用独立分量分析的思想和方法,提出了利用FastICA算法提取光谱信号的特征的方案,并进行了详细的仿真实验。 此外,我们还进行了进一步的研究,探索了其他可能的ICA应用领域,如音乐信号处理、图像处理以及金融数据分析等。通过在这些领域中的实验和应用,我们发现ICA在提取信号特征、降噪和信号分离等方面具有广泛的潜力和应用前景。
<think>好的,用户需要查找关于P5785 [SDOI2012] 任务安排的题解或解析。首先,我应该回忆一下这个题目的内容。这是一道动态规划优化的问题,可能涉及斜率优化或者凸包优化。题目大致是将任务分批处理,每批任务有一个启动时间,需要最小化总成本。 接下来,我需要检查用户提供的引用内容是否有相关的信息。引用[1]提到了并查集和另一道题目,可能与本题无关。引用[2]提到了一种O(n√n)的算法,但不确定是否适用于此任务安排问题。引用[3]是关于kubeneter的,明显不相关。因此,用户提供的引用可能没有直接帮助,需要依靠其他知识。 题目模型通常是这样的:有n个任务,每个任务需要时间t_i和费用c_i。将任务分成若干批,每批开始前有S的启动时间。每批的总费用是该批完成时刻乘以该批任务的总费用系数。目标是最小化总费用。 动态规划的状态设计一般是dp[i]表示处理前i个任务的最小费用。转移方程需要考虑分批次,可能会是dp[i] = min(dp[j] + (sumT[i] + S * k) * (sumC[i] - sumC[j])),其中k是分了几批,但这里可能需要更巧妙的处理,比如将启动时间的影响提前计算,以避免依赖批次数量。这种方法叫做“费用提前计算”。 时间复杂度方面,如果直接使用O(n^2)DP,对于n较大的情况会超时,所以需要优化。常见的优化方法包括斜率优化,将转移方程转换为线性形式,维护一个凸包,利用单调队列来快速找到最优转移点。 具体步骤可能是这样的: 1. 预处理前缀和数组sumT和sumC。 2. 设计状态转移方程,考虑费用提前计算,例如将后续的启动时间S的影响提前加到当前批次。 3. 将转移方程转换为斜率优化的形式,比如形如dp[j] = (dp[i] - ...) 的表达式,找到斜率相关的项。 4. 维护一个单调队列,保持候选转移点的斜率单调,从而每次可以在O(1)时间内找到最优的j。 需要注意的点包括斜率计算时的除法可能导致的精度问题,可以用交叉相乘来比较斜率的大小。此外,sumT可能不是单调的,这时候可能需要维护凸包的下凸壳,或者使用其他方法如CDQ分治,但题目中可能由于数据范围允许而采用单调队列优化。 可能还需要考虑sumT的前缀和是否会有负数,这会影响时间复杂度的分析。如果sumT是单调递增的,那么可以使用单调队列,否则可能需要更复杂的结构。 最后,根据这些分析,给出具体的状态转移方程和优化步骤,以及可能的代码结构。同时,提醒用户注意数据范围和可能的实现细节,比如使用long long类型避免溢出。</think>### P5785 [SDOI2012] 任务安排 题解 #### 题目分析 题目要求将$n$个任务分批处理,每批任务启动需要时间$S$,每批的总费用为该批完成时刻乘以其费用系数之和。目标是设计动态规划算法并用斜率优化实现$O(n)$时间复杂度。 #### 关键思路 1. **费用提前计算**:将后续分组的启动时间影响提前计入当前状态,转移方程为: $$dp[i] = \min_{0 \leq j < i} \{ dp[j] + sumT[i] \cdot (sumC[i] - sumC[j]) + S \cdot (sumC[n] - sumC[j]) \}$$ 其中$sumT$和$sumC$为前缀和数组[^1]。 2. **斜率优化**:将方程改写为线性形式,设$j$为最优转移点,则有: $$dp[j] = (sumT[i] + S) \cdot sumC[j] + dp[i] - sumT[i] \cdot sumC[i] - S \cdot sumC[n]$$ 转化为$y = kx + b$形式后,维护单调队列中的凸包点,保证斜率$sumC[j]$单调递增[^2]。 #### 实现步骤 1. **预处理前缀和**: ```python sumT = [0] * (n+1) sumC = [0] * (n+1) for i in 1..n: sumT[i] = sumT[i-1] + t[i] sumC[i] = sumC[i-1] + c[i] ``` 2. **维护单调队列**: - 在队列中删除斜率小于当前斜率的点 - 取队首作为最优转移点 - 将新点加入队列,维护凸包形状 #### 参考代码片段 ```python q = deque() q.append(0) dp[0] = 0 for i in 1..n: # 删除队首非最优项 while len(q) >= 2 and slope(q[0], q[1]) <= sumT[i] + S: q.popleft() j = q[0] dp[i] = dp[j] + sumT[i]*(sumC[i]-sumC[j]) + S*(sumC[n]-sumC[j]) # 维护凸包 while len(q) >= 2 and slope(q[-2], q[-1]) >= slope(q[-1], i): q.pop() q.append(i) ``` #### 复杂度分析 通过斜率优化将时间复杂度从$O(n^2)$降为$O(n)$,适用于$n \leq 3 \times 10^5$的数据范围。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值