【问题描述】
小 T 是一名质量监督员,最近负责检验一批矿产的质量。这批矿产共有n 个矿石,从1到n 逐一编号,每个矿石都有自己的重量wi 以及价值vi。检验矿产的流程是:见图
若这批矿产的检验结果与所给标准值S 相差太多,就需要再去检验另一批矿产。小T不想费时间去检验另一批矿产,所以他想通过调整参数W 的值,让检验结果尽可能的靠近标准值S,即使得S-Y 的绝对值最小。请你帮忙求出这个最小值。
【样例输入】
5 3 15
1 5
2 5
3 5
4 5
5 5
1 5
2 4
3 3
【样例输出】
10
【解题思路】
本题为NOIP2011day2第二题,可能很多人不会的原因是因为公式看不懂,先解释一下,Yi的公式是指(符合条件的j的总数)*(vj的和)。现在我们来分析一下题目。题目中说,让我们找一个合适的W的值使得Yi的和与给定的S之差的绝对值最小。由于题目中说j的要求是区间中Wi大于W的矿石,因此,我们可以确定W的范围为minwi-1~maxwi+1。在minwi-1以下的与minwi-1的值相同(全部可以取),同理,maxwi+1以上的与maxwi的值相同(全部不能取)。由于题目中说S-Y的绝对值最小,因此我们可以肯定Y可能>S也可能<S,那么我们将W所可以取的区间二分,如果所得到的Y>S,我们就往右边的区间找,否则往左边的区间找,50分就到手了。
剩下的50分需要用到一个强大的技巧,也是许多区间问题中非常重要的一个技巧,那就是前缀和。当我们枚举所有矿石中Wi>W的矿石的时候,用一个数组存储,将所有的都算出来,这样,在求某个区间的时候,就只要将右节点的值减去左节点-1的值,即所求的区间了,整个过程是n+m,比m*(r-l)快多了。
【代码实现】
1 type rec=record 2 w,v:int64; 3 end; 4 var i,j:longint; 5 sum,min,w,v,n,m,s,minw,maxw,l,r,mid:int64; 6 a,b:array[0..200010] of rec; 7 sumv,sumw:array[0..200010] of int64; 8 function get(x:int64):int64; 9 var i,j:longint; 10 begin 11 fillchar(sumv,sizeof(sumv),0); 12 fillchar(sumw,sizeof(sumw),0); 13 get:=0; 14 for i:=1 to n do 15 if a[i].w>x then 16 begin 17 sumv[i]:=sumv[i-1]+a[i].v; 18 sumw[i]:=sumw[i-1]+1; 19 end 20 else 21 begin 22 sumv[i]:=sumv[i-1]; 23 sumw[i]:=sumw[i-1]; 24 end; 25 for j:=1 to m do 26 inc(get,(sumw[b[j].v]-sumw[b[j].w-1])*(sumv[b[j].v]-sumv[b[j].w-1])); 27 end; 28 begin 29 minw:=maxlongint; 30 readln(n,m,s); 31 for i:=1 to n do 32 with a[i] do 33 begin 34 readln(w,v); 35 if w>maxw then 36 maxw:=w; 37 if w<minw then 38 minw:=w; 39 end; 40 for i:=1 to m do 41 with b[i] do 42 readln(w,v); 43 l:=minw-1; 44 r:=maxw+1; 45 min:=9223372036854775800; 46 repeat 47 mid:=(l+r) div 2; 48 sum:=get(mid); 49 if abs(sum-s)<min then 50 min:=abs(sum-s); 51 if sum>s then 52 l:=mid+1 53 else 54 r:=mid-1; 55 until l>r; 56 writeln(min); 57 end.