P5785 [SDOI2012]任务安排

link

代码还是有些问题…

/*
数组没开够,爆零两行泪
ll开成 int,爆零两行泪
多组忘清空,爆零两行泪
 dp 没初值,爆零两行泪
深搜没边界,爆零两行泪
广搜忘出队,爆零两行泪
输入没加 &,爆零两行泪
模数没看见,爆零两行泪
 -1 不输出,爆零两行泪
越界不特判,爆零两行泪
空间开一倍,爆零两行泪
无向变有向,爆零两行泪
题意没审清,爆零两行泪
文件名起错,爆零两行泪
调试忘删除,爆零两行泪
文件不保存,爆零两行泪
文件不读入,爆零两行泪
文件不输出,爆零两行泪
*/
#include<bits/stdc++.h>
using namespace std;
typedef long long ll; 
const int N=3e5+13;
int n,s;
ll c[N],t[N];//sumc,sumt 
ll f[N];
int q[N];
int main(){
	cin>>n>>s;
	for(int i=1;i<=n;i++){
		cin>>t[i]>>c[i];
		t[i]+=t[i-1];
		c[i]+=c[i-1];//处理前缀和 
	}
	int hh=0,tt=0;
	q[0]=0;//f(0)点
	for(int i=1;i<=n;i++){
		int l=hh,r=tt;
		while(l<r){
			int mid=(l+r)>>1;
			if(f[q[mid+1]-f[q[mid]]]>=(t[i]+s)*(c[q[mid+1]]-c[q[mid]])) r=mid;
			else l=mid+1;
		}
		int j=q[r];
		f[i]=f[j]-(t[i]+s)*c[j]+t[i]*c[i]+s*c[n];//见式,不解释
		//删去此前的线(点),维护凸包性质 
		while(hh<tt&&(f[q[tt]]-f[q[tt-1]])*(c[i]-c[q[tt]])>=(f[i]-f[q[tt]])*(c[q[tt]-c[q[tt-1]]])) tt--;
		//上面这是处理一下斜率,防止精度挂掉
		q[++tt]=i;
	} 
	cout<<f[n]<<endl;
	return 0;
}
/*	考虑dp 
 *	状态设计: f[i]
 *		集合:将前i个任务处理完的方案数 
 *		属性:费用最小值 
 *	状态计算: f[i] 
 *		不同点:最后一组(当前)的划分情况 
 *		分类: j∈{0,1,2,...,i-1} 前j一个任务已经划分为一组 
 *		不变的部分:前k组的最小花费(不包括当前组)
 *		求变化的部分:sumt[j]*(sumc[i]-sucm[j])+S*(sumc[N]-sumc[j])
 *		                做当前任务的费用         机器冷却的费用 
 *	sumt:t的前缀和 
 *	sumc:c的前缀和 
 *	f[i]=min{f[j]+sumt[i]*(sumc[i]-sumc[j])+S*(sumc[N]-sumc[j]} 
 *	复杂度O(n^2) 
 *	
 *	考虑优化,我们需要找到j,使得f[j] 
 *	f[i]=f[j]-(sumt[i]+s)*sumc[j]+sumt[i]*sumc[i]+s*sumc[n];
 *	       y                   x
 *	f[j]=(sumt[i]+s)*sumc[j]+f[i]-sumt[i]*sumc[i]-s*sumc[n];
 *	y   =     k         x   +           b
 *	y       斜率                      截距 
 *	k为正整数 ,且固定 f(i)不固定 
 *	所以f(j)必过 1,3象限 
 *	j∈{0,1,2,...,i-1} =>(f[0],sumc[0]),(f[1],sumc[1])...
 *	目标:f[i]最小
 *	分析: 
 *	随便取一点(x0,y0) 
 *	我们可以平移直线,直到经过(x0,y0),然后求得截距,并求得f(i) 
 *	由于f(i)为正,截距符号为负,则可知截距越大,f(i)越大 
 *	为是f(i)最小,我们需要使截距尽可能大(数值),低(实际值,位置) 
 *	由此观之,对于每个i,先求的斜率,再向上平移,直到遇到第一个接触到的点(截距最低) 
 *	(图一) 
 *	但并不是每个点都会被碰到(产生贡献) 
 *	因此需要排除不论斜率是多少,都不会被碰到(产生贡献的) 
 *	怎么排除?任选两个点连线,可以删去该线上方的点(凸包) 
 *	因此我们只需维护凸包的下边界(核心思想) 
 *	但在这种情况下,最坏情况还是会T 
 *	不过我们可以看到,直线斜率介于k1,k2之间 
 *	所以我们需要找到第一个斜率大于k的点 
 *	(图二)
 *	在这个凸包下边界中,斜率是单调的,于是可以二分找到我们需要的点 (常规思路)
 *	结合本题,两个sum数组也是单调的,这意味着我们从前往后枚举i时,斜率是单调递增的
 *	又由于c是大于零的正整数sumc[i]也是递增的,在图像上体现为:下一个点在当前点的右侧(点的横坐标单调递增) 
 *	所以本题可以不用二分 
 *	递增的斜率...(很重要一个条件) 
 *	在枚举某根线时,这根线的斜率若比凸包的某一根线的斜率大 
 *	那么可以删掉这根线下面的点(下面的线斜率都比这根小,因此以后一定不会被用到,因为后面的线的斜率更大)
 *	在查询时,可以把上述(对头小于当前斜率)的点从队头删掉 
 *	在插入的时候,有横坐标单调的性质可知,该点一定不会被删掉,所以我们只需更新其他的点(将队尾所有不在凸包上的点全部删掉) 
 *	每个点只会出队入队一次,复杂度O(n) 
 *	(图三) 
 *	实现细节: 
 *	(f(2)-f(1))/(sumc(2)-sumc(1))<=sumt(i)+s(队头判断斜率更小的点)
 *	(f(tt)-f(tt-1))/(sumc(tt)-sumc(tt-1))>=(f(i)-f(tt))/(sumc(i)-sumc(tt))(插入时维护凸包删边性质) 
 *	q[tt]表示凸包最右上的点的编号
 *	q[hh]表示凸包最左下点的编号 
 *	(啊我累死了) 
 */ 

图一
图二
图三

posted @ 2020-10-31 19:33  actypedef  阅读(32)  评论(0编辑  收藏  举报