ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

[SDOI2012]任务安排(山东省选)斜率dp

2022-01-06 23:34:42  阅读:277  来源: 互联网

标签:qu int mid 斜率 SDOI2012 tm fm dp


一键跳转至题目

题目描述
机器上有 $n$ 个需要处理的任务,它们构成了一个序列。这些任务被标号为 $1$ 到 $n$,因此序列的排列为 $1 , 2 , 3 \cdots n$。这 $n$ 个任务被分成若干批,每批包含相邻的若干任务。从时刻 $0$ 开始,这些任务被分批加工,第 ii 个任务单独完成所需的时间是 $T_i$ 。在每批任务开始前,机器需要启动时间 $s$,而完成这批任务所需的时间是各个任务需要时间的总和。
注意,同一批任务将在同一时刻完成。 每个任务的费用是它的完成时刻乘以一个费用系数 $C_i$。请确定一个分组方案,使得总费用最小。

输入格式
第一行一个整数 $n$。 第二行一个整数 $s$。接下来 $n$ 行,每行有一对整数,分别为 $T_i$ 和 $C_i$,表示第 ii 个任务单独完成所需的时间是 $T_i$ 及其费用系数 $C_i$ 。

输出格式
一行,一个整数,表示最小的总费用。

输入输出样例
输入 #1

5
1
1 3
3 2
4 3
2 3
1 4

输出 #1

153

说明/提示
对于 $100%$ 数据,$1 \le n \le 3 \times 10^5$ ,$1 \le s \le 2^8$ ,$\left| T_i \right| \le 2^8$ ,$0 \le C_i \le 2^8$ 。


思路(注:结尾有完整AC代码,一定要看到最后!!!)

显然,$n$的最大值是$3 \times 10^5$,暴力必然会炸
那么何为“斜率优化”呢?
答曰:用线性规划优化dp式。
顾名思义,斜率dp就是将题目给出的信息转换到坐标系中,判断斜率求解思路就这么讲完了 ,乍一看这个思路很突兀,那么我们来详细的进行分析。
首先我们根据提议可以得出这么个dp式子:

dp[i] = min(dp[i], dp[j] + tm[i] * (fm[i] - fm[j]) + (fm[n] - fm[j]) * s);

其中$dp_i$用来记录到$i$点为止的最优解,$tm_i$记录的是到$i$的$t$数组前缀和,$f_i$同理,$j$点只是中间的一个分割点,$tm_i\times(fm_i-fm_j)$是求这段区间的总耗费时间,最后一个式子至关重要,我们居然已经会在$i$这个为止分割一次,那么我们可以直接累加后面节点的$s$(机器启动耗费的时间)即为$(fm_n-fm_j)\times s$,这个思路是个人也能听懂
目前为止我们即可以得到:

for (int i = 1; i <= n; ++i) {
	for (int j = 0; j < i; ++j) {
		dp[i] = min(dp[i], dp[j] + tm[i] * (fm[i] - fm[j]) + (fm[n] - fm[j]) * s);
	}
}

这样写出来的代码即可以在洛谷得到20分,其他点全T掉。


显然,学过dp的人都能看出来,这个代码只是个普通的线性dp,那么接下来我们再利用这段代码推出斜率dp的代码。
通过上面的动态转移方程我们可以推出:

dp[i]=dp[j]-(tm[i]+s)fm[j]+tm[i]fm[i]+fm[n]*s;

拆开合并同类项而已,小学生也能推出来
学过斜率dp的大佬都能发现,这个式子符合函数的基本形式:$y=kx+b$,我们将(tm[i]+s)看作k,将其余项看作b,既可以得出这个函数的斜率为(tm[i]+s)
这样我们就可以推出:对于每一个$i$我们都可以将dp[i]看作这个节点纵坐标,将fm[i]看作这个点的很坐标,于是我们可以将每个点放到坐标系中(非样例草图,有点难看凑活看吧):
非样例草图
那么我们再将函数图像带入坐标系中
在这里插入图片描述

对于这条 dp[i]=dp[j]-(tm[i]+s)fm[j]+tm[i]fm[i]+fm[n]*s 的函数图像,要想球的dp最优解就要看这个函数图像先碰到哪个点,但是要将每个图像中的每个点进行比较那就太耗时间了,于是这里我们就会用到斜率的知识

在这里插入图片描述

红色的线连接的点为需要进行判断的点,上面那个蓝色的则是不需要判断的点。
在这里插入图片描述

显然tan∠ACD > tan∠BCD(未学过tan点击次链接
所以判断两点之间的斜率我们就只用判断对角边/斜边的值就可以了($CD=x_c-x_c$,$BD=y_b-y_d$),这里我们可以用队列来维护这些点(这段操作代码中很详细),所以针对每个$i$,队列中每个tan的值就必须<=(tm[i]+s)(函数的斜率,之前讲过),然而在插入一个元素时就要从队尾倒着找到第一个tan>=tan(当前点)的值然而<=tan(当前点)的值就直接踢出队列,因为已经没有比较的必要了,于是到现在我们就可以得到斜率优化后的代码了:

for (int i = 1; i <= n; ++i) {
		int j = 0;
		while (l < r && dp[qu[l + 1]] - dp[qu[l]] <= (tm[i] + s) * (fm[qu[l + 1]] - fm[qu[l]]))
			l++;
		j = qu[l];
		//dp[i] = min(dp[i], dp[j] - (s + tm[i]) * fm[j] + tm[i] * fm[i] + s * fm[n]);
		dp[i] = dp[qu[l]] - (s + tm[i]) * fm[qu[l]] + tm[i] * fm[i] + s * fm[n];
		//while(l<r&&)
		while (l < r && (dp[qu[r]] - dp[qu[r - 1]])* (fm[i] - fm[qu[r]]) >= (dp[i] - dp[qu[r]]) * (fm[qu[r]] - fm[qu[r - 1]]))
			r--;
		qu[++r] = i;
	}

注:代码中的判断用*是为了防止浮点运算
然而用了这个方法的oier会发现只能过$60%$的点,其余点全WA,然而在我们之前的推到中忽略了和纵坐标(纵坐标具体数值前面已提过)为负数的情况
在这里插入图片描述
如图中的点E那么就会导致找最近点时,比较tan出锅了,于是,在第一个while循环中,我们改用二分的思路,整体思路和之前一样,直接上代码:

int bs(int ll, int rr, int ss) {//求j
	int mid;
	int res=qu[r];
	while (ll <= rr) {
		//l++;
		mid = (ll + rr) / 2;
		if (dp[qu[mid + 1]] - dp[qu[mid]] >= ss * (fm[qu[mid + 1]] - fm[qu[mid]])) {
			res = qu[mid];
			rr = mid - 1;
		}
		else
			ll = mid + 1;
	}
	return res;
}

看到这里的oier们大概已经理解了,若还不理解可以结合完整代码进行理解:

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<cstdio>
#pragma warning(disable:4996)
using namespace std;
#define int long long
int n, s, tm[1000010], fm[1000010], ans = 0x3f3f3f3f, dp[1000010], qu[1000010], l, r=0;
struct node
{
	int t, f;
}edge[1000010];
int bs(int ll, int rr, int ss) {
	int mid;
	int res=qu[r];
	while (ll <= rr) {
		//l++;
		mid = (ll + rr) / 2;
		if (dp[qu[mid + 1]] - dp[qu[mid]] >= ss * (fm[qu[mid + 1]] - fm[qu[mid]])) {
			res = qu[mid];
			rr = mid - 1;
		}
		else
			ll = mid + 1;
	}
	return res;
}
signed main() {
	scanf("%lld%lld", &n, &s);
	for (int i = 1; i <= n; ++i) {
		scanf("%lld%lld", &edge[i].t, &edge[i].f);
		tm[i] = tm[i - 1] + edge[i].t;
		fm[i] = fm[i - 1] + edge[i].f;
	}
	memset(dp, 0x3f3f3f3f, sizeof(dp));
	dp[0] = 0;
	/*for (int i = 1; i <= n; ++i) {
		for (int j = 0; j < i; ++j) {
			dp[i] = min(dp[i], dp[j] + tm[i] * (fm[i] - fm[j]) + (fm[n] - fm[j]) * s);
		}
	}*/
	//dp[i]=dp[j]-(tm[i]+s)*fm[j]+tm[i]*fm[i]+fm[n]*s;
	qu[l] = 0;
	qu[++r] = 0;
	for (int i = 1; i <= n; ++i) {
		int j = bs(l, r, (tm[i] + s));
		dp[i] = dp[j] + tm[i] * (fm[i] - fm[j]) + s * (fm[n] - fm[j]);
		while (l < r && (dp[qu[r]] - dp[qu[r - 1]])* (fm[i] - fm[qu[r]]) >= (dp[i] - dp[qu[r]]) * (fm[qu[r]] - fm[qu[r - 1]]))
			r--;
		qu[++r] = i;
	}
	cout << dp[n];
	return 0;
}

各位读到最后的oier们点个赞吧qwq
在这里插入图片描述

标签:qu,int,mid,斜率,SDOI2012,tm,fm,dp
来源: https://www.cnblogs.com/I-am-yzh/p/15773333.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有