标签:begin end -- sum list 斜率 tail 队列 Dp
你有n个任务需要处理,你有一台计算机能够处理这些任务,由于这些任务的特殊性,你只能按照顺序分批处理这些任务,不能先处理编号大的任务再处理编号小的。每个任务有一个处理难度值ai,计算机处理一批任务的总耗时是这批任务中所有任务的难度值之和的平方加上一个常量C。请你写一个程序合理的分批,使得计算机将所有任务都处理完的时间最少。
输入格式:第一行有2个整数n,c。第二行n个整数,a1,a2,a3,a4,a5,a6……an
输出格式:一行,输出最少的耗时。
Tasks.in
3 5
2 1 2
Tasks.out
23
说明:任务1,2一批,任务3一批。时间=(1+2)^2+5+2^2+5=23
数据范围:
40% n<=1000
100% n<=100000
所有数据的答案在INT64范围内
分析:
注意题目中出现了一句话“由于这些任务的特殊性,你只能按照顺序分批处理这些任务,不能先处理编号大的任务再处理编号小的。”这句话非常明显的道出了题目的无后效性,因而用动态规划解这样的题目,也就是顺理成章了。
下面我们不加推导的写出一个动态规划方程,相信大家都能够非常轻松的理解。
F[i]=min{f[j]+(sum[i]-sum[j])^2}+c
我们的目标是尽量将前面那一段更尽可的小,明显这个方程O(n^2),不能满足题目的要求。
设v[j]=f[j]+(sum[i]-sum[j])^2
我们不妨来考虑一下一般情况,即当x>y时且v[x]>v[y]。
于是乎 f[x]+(sum[i]-sum[x])^2>f[y]+(sum[i]-sum[y])^2
于是乎 f[x]-f[y]>(sum[i]-sum[y])^2-(sum[i]-sum[x])^2
于是乎 f[x]-f[y]>(2*sum[i]-sum[x]-sum[y])(sum[x]-sum[y])
于是乎 f[x]-f[y]>2*sum[i]*(sum[x]-sum[y])-sum[x]^2+sum[y]^2
于是乎 f[x]+sum[x]^2-f[y]-sum[y]^2>2*sum[i]*(sum[x]-sum[y])
于是乎 ((f[x]+sum[x]^2)-(f[y]+sum[y]^2))/sum[x]-sum[y]>2*sum[i]
不等式左边不难发现很像求斜率的公式,如果把其抽象成点,就成为了以sum[j]为x坐标,f[j]+sum[j]^2为y坐标
那么条件我们也可以知道了,就是将x和y这2个点连出一条线,这条线的斜率如果大于2*sum[i],
那么我们可以说在i的转移中v[x]>v[y],也就是说v[y]优于v[x],也就是说x标号小的结点应该被保留下来,
对于程序中来说就是队列头那种点。
如何插入元素,并且维护队列?
当我们算出一个新的f[i]的时候,为了“造福后代”,我们需要将其加入到队列中,
加入队列后有2种情况(如下图1及图2):
(图1)
(图2)
假设我们设最后一个点为w,倒数第二个点为u。
图1中因为新加入的点与原队列中最后一个点构成的线段的斜率比原来最后一条线段的斜率大,
因为当前情况下w有意义,这样的情况的队列就是一个满足条件的队列。
图2中与图1正好相反,所以这样加入一个新的点,(这个新点与W之间的斜率不满足上面那个式子,
则说明这个新点比W更优,W的存在是没有意义的)会使得w这个点失去意义,
所以我们可以删掉w这个点,再不断做下去,知道最后几个点出现左图情况就可以了。
解决了上面2个问题,队列也就维护出来了,动态规划转移也可以完成了,下面我们来分析一下时间复杂度。
因为每个元素最多进队列一次,出队列一次,因此进行队列操作的复杂度是O(n),状态O(n)级别,转移取队首元素O(1)完成,动态规划复杂度也是O(n),所以总的复杂度为O(n)。
到此这个题目已经完美解决。
var sum , f : array[0..100001] of int64; list : array[0..100001] of longint; n , c , head , tail : longint; procedure init; var i , temp : longint; begin readln( n , c ); fillchar( sum , sizeof( sum ) , 0 ); for i := 1 to n do begin read( temp ); sum[i] := sum[i - 1] + temp; end; readln; end; function getk( a , b : longint ) : double; //a,b..........i begin getk := ( f[b] - f[a] + sqr( sum[b] ) - sqr( sum[a] ) ) / ( sum[b] - sum[a] ); end; procedure del( now : longint ); begin while ( head < tail ) and ( getk( list[head] , list[head + 1] ) < now ) do inc( head ); end; procedure add( now : longint ); var t1 , t2 : double; begin repeat if head >= tail then break; t1 := getk( list[tail - 1] , list[tail] ); t2 := getk( list[tail] , now ); if t1 < t2 then //对应图1 break else //对应图2 dec( tail ); until false; inc( tail ); list[tail] := now; end; procedure Dp; var i , temp : longint; begin head := 1; tail := 1; fillchar( list , sizeof( list ) , 0 ); for i := 1 to n do begin del( sum[i] * 2 ); //当i的值增加sum[i]变大,这时队列头结点有可能不再是最优决策, //这时要删除头结点.找到一个最优的决策 f[i] := f[list[head]] + sqr( sum[i] - sum[list[head]] ) + c; add( i ); end; end; procedure print; begin writeln( f[n] ); end; procedure main; begin init; Dp; print; end; begin main; end.
标签:begin,end,--,sum,list,斜率,tail,队列,Dp 来源: https://www.cnblogs.com/cutemush/p/13744033.html
本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享; 2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关; 3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关; 4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除; 5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。