第三题:帮忙
背景:
在高考研讨会上,随着 z 同学对 zn 了解的深入,发现她不仅是一个善良的女孩子,还 是一个很勤劳的女生,哇塞,这样棒的女生在21世纪真是太难遇到了,要是可以娶到这样的 女生,小 z 陷入了无限的 yy 中
高考研讨会进入了尾声,校长一声令下,搬椅子喽~~~~,只见平时道貌岸然的那些所
谓的好学生们一听要干活全跑了,shit~~!!操场上只剩 zn 与 z 同学了,面对成千上万的椅子, 肿么办~~小 z 突然意识到现在正是奥赛课啊,于是他想到了 hzoi2009的兄弟们,果然,当小 z 跑到机房,说明来意后,hzoi2009的兄弟们(还有个妹子)当时貌似在考试,但都没有半点 迟疑,立马跟随小 z 下楼(小 z 一直把这件事记在心中,现在回想起来还有点小感动哈)
题目描述:
面对成千上万的椅子,当然不能用蛮力一个一个的搬了,校长大人提供了一种搬运车。 椅子们是排成一排的,搬运车一次必须搬不少于k 个的连续的椅子,消耗的机油为所搬椅子重量的平均值(总重量比椅子个数)。小 z 想知道搬一次最多能耗费多少机油,好向校长报销机油费
输入:
第一行:整数 n(代表椅子数)与 k(一次搬的最少的椅子数) 接下来 n 行,每行一个整数 w i,代表椅子的重量
输出:
搬一次可能耗费的最多的机油数*1000(结果取整) 样例输入:
10 6
6
4
2
10
3
8
5
9
4
1
样例输出:
6500
数据范围:
对于30%的数据 0<k<N<=1000
对于100%的数据 0<k<n<100 000
0<Wi<=2000
注意,此处 ans 取整的时候一定要用 trunc 而不能四舍五入,血的教训啊!
SueMiller:直接枚举这个区间, 40 。
用一个很没技术含量的,根本不对的动规:用 f[i] 表示到第 i 个节点,而且选取这个节点,合法的一段数串的最大的平均值,用 g[i] 表示这一个合法的书穿的较小边界。
然后可以分析,每一个数串的最优值极有可能是以下两种情况之一:从这个数串开始,往前找长度为 k 的数串的平均值;f[i-1] 的数串再加上这一个。
然后,就可以转移了。 80
代码:(SueMiller)
program ACRush;
var n,k:longint;
a,g:array[0..100000]of longint;
f:array[0..100000]of longint;
i,j:longint;
ans:longint;
begin
assign(input,'help.in');reset(input);
assign(output,'help.out');rewrite(output);
readln(n,k);
fillchar(a,sizeof(a),0);
fillchar(f,sizeof(f),0);
for i:=1 to n do
begin
readln(a[i]);
a[i]:=a[i]+a[i-1];
end;
{ ans:=0;
for i:=0 to n-k do
for j:=i+k to n do
begin
if trunc((a[j]-a[i])/(j-i)*1000)>ans then ans:=trunc((a[j]-a[i])/(j-i)*1000);
end;
writeln(ans); } ------>这是枚举,40
fillchar(g,sizeof(g),0);
f[k]:=trunc(a[k]/k*1000);
g[k]:=1;
ans:=f[k];
for i:=k+1 to n do
begin
if (a[i]-a[g[i-1]-1])/(i-g[i-1]+1) > (a[i]-a[i-k])/k then
begin
f[i]:=trunc((a[i]-a[g[k-1]-1])/(i-g[k-1]+1)*1000);
g[i]:=g[i-1];
end
else begin
f[i]:=trunc((a[i]-a[i-k])/k*1000);
g[i]:=i-k+1;
end;
if f[i]>ans then ans:=f[i];
end;
writeln(ans);
close(input);close(output);
end.
然后,我们分析这个根本不对的算法,他不对的地方就在于这个东西有后效性,有可能你再上一个最优数串中,加入了一个出奇的小的值,导致它的平均值变小了许多,而在你往前追溯 k 个数的时候,可能往前的 k+1 个是一个介于 f[i-1] 和 f[i] 之间的值。也就是说,这一个值,对于 f[i-1] 来说,是在最优值之下的,不能选取。但是,对于 f[i] 来说,它却是可以用于更新的最优值,这种情况就挂了。
所以,对于这个算法的改进就是,再向前比较寻找,但这样就成了 O(n^2)
然后,理所当然需要优化。
计算一下,将每一个区间的数值总和和这个区间的数值个数分别当做 x 轴和 y 轴,轻易可以看出,我们要求的就是他的斜率,所以斜率越大越好。
这样一来,可以每次掐出一个 k 长度的区间,如果它的斜率比当前值更优,就更新当前值,将已知数串的上界改变,就这样依次将斜率往上叠加,利用一下导数的思想,然后及时更新最优值。
其实这个算法,是一种算法的变形:求不限长度的最大子序和:从第一个值开始,依次往后叠加,如果大于当前值就更新当前值,如果小于 0 就转赋值成 0 ,这个只不过对长度加了一个限制,外加变成了斜率。
代码 (LYF)
var
s:array[0..100000] of longint;
n,m,i,j,k:longint;
ans:double;
begin
assign(input,'help.in'); reset(input);
assign(output,'help.out'); rewrite(output);
readln(n,m);
for i:=1 to n do
begin
readln(k);
s[i]:=s[i-1]+k;
end;
for i:=0 to n-m do
begin
k:=i+m;
if (s[k]-s[i])*(k-j)>(s[k]-s[j])*(k-i) then j:=i;
if (s[k]-s[j])/(k-j)*1000>ans then ans:=(s[k]-s[j])/(k-j)*1000;
end;
writeln(trunc(ans));
close(input); close(output);
End.
然后,如果这个听不懂,还有一个比较好理解的。
二分答案,二分那个平均值是多少(注意 0<Wi<=2000 ),然后判断,将各个项都减去这个平均值,再求出每一个区间的各个项的和,如果有比这个值大的就将这个值往大里找,反之往小里找,就这样,一直到上下界重合,就可以了。
代码 (Dsqwwe)
program dsqwwe;
var
sum,b,f:array[0..100005] of extended;
a:array[0..100005] of longint;
l,r,mid,max,min:extended;
n,m,i:longint;
function maxx(a,b:extended):extended;
begin
if a>b then exit(a)
else exit(b);
end;
function check(val:extended):boolean;
var
i:longint;
begin
fillchar(sum,sizeof(sum),0);
for i:=0 to n do
f[i]:=-maxlongint;
for i:=1 to n do
begin
b[i]:=a[i]-val;
sum[i]:=sum[i-1]+b[i];
end;
f[m]:=sum[m];
if f[m]>-0.00000001 then exit(true);
for i:=m+1 to n do
begin
f[i]:=maxx(f[i-1]+b[i],sum[i]-sum[i-m]);
if f[i]>-0.0000001 then exit(true);
end;
exit(false);
end;
begin
assign(input,'help.in');
reset(input);
assign(output,'help.out');
rewrite(output);
readln(n,m);
min:=maxlongint; max:=0;
for i:=1 to n do
begin
readln(a[i]);
a[i]:=a[i]*1000;
if a[i]>max then max:=a[i];
if a[i]<min then min:=a[i];
end;
l:=min; r:=max;
while r-l>=0.0000001 do
begin
mid:=(r+l)/2;
if check(mid) then
l:=mid
else r:=mid;
end;
mid:=mid+0.000001;
writeln(trunc(mid));
close(input);
close(output);
end.