用单调性优化动态规划 poj3017

来源:互联网 发布:win7 windows temp 编辑:程序博客网 时间:2024/09/21 09:04

题意:将长度为n的序列分成若干段,使每段之和不大于m,且使各段中最大值之和最小,输出该值。

显然的方程f[i]:=min(f[j]+max(j+1,i));f[i]表示前i个中划分出的最小值,max(l,r)只l,r中的最大值,转移条件为sum[j+1,i]<=m。

这个方程时间复杂度为o(n^2)的,承受不了,所以要优化。

设b[i]为能转移到i的最小的j,由于所有的数均为正整数,所以b数组是递增的,同时,f数组也可证出是非降的(设将a[i]从f[i]中拿出即可得到f[i-1],则若a[i]为f[i]最后一段中最大的,则f[i-1]<f[i],若不是则f[i-1]=f[i],得证)。

考虑一种情况,我们用f[j]推f[i]时,a[j]<=max(j+1,i),则将a[j]并入后一段不会对最大值造成影响,而f[j-1]<=f[j],由f[j-1]推f[i]必定更优。

因此根据以上性质,我们维护一个单调队列,通过b值从队首退元素,通过a值递减,从队尾退元素,因为a是递减的所以有个很优美的性质max(j+1,i)=j在队列中后一个j‘的a值,有了这个性质不管是转移还是维护都好实现——转移时直接加,维护时,只需对最后一个元素修改。

队首决策不一定最优,所以用数据结构维护,我用的是线段树。

var a,d,b:array[0..100000]of longint;    f,s:array[0..100000]of int64;    b1:array[1..262144]of int64;    n,m,m1:int64;function search(x:longint):longint;var l,r,mid:longint;begin l:=1;r:=x; while l<=r do begin  mid:=(l+r)>>1;  if s[x]-s[mid-1]>m then l:=mid+1 else r:=mid-1 end; exit(l)end;procedure origin;var i:longint;begin fillchar(b,sizeof(b),0);fillchar(s,sizeof(s),0); s[0]:=0;for i:=1 to n do s[i]:=s[i-1]+a[i]; i:=1; while (s[i]<=m)and(i<=n) do begin b[i]:=0;inc(i) end; for i:=i to n do b[i]:=search(i)-1; m1:=1; while m1<n+2 do m1:=m1<<1;m1:=m1-1; fillchar(b1,sizeof(b1),127)end;function mini(x,y:longint):longint;begin if x<y then exit(x) else exit(y)end;procedure update(x:longint);var w:longint;begin w:=f[d[x]]+a[d[x+1]]; x:=x+m1;b1[x]:=w;x:=x>>1; while x<>0 do begin  b1[x]:=mini(b1[x<<1],b1[x<<1+1]);x:=x>>1 endend;function ask(l,r:longint):longint;begin if l>r then exit(maxlongint); l:=l+m1-1;r:=r+m1+1;ask:=maxlongint; while not(l xor r =1) do begin  if l and 1=0 then if b1[l+1]<ask then ask:=b1[l+1];  if r and 1=1 then if b1[r-1]<ask then ask:=b1[r-1];  l:=l>>1;r:=r>>1 endend;procedure dp;var h,r:longint;    i:longint;    min:int64;begin fillchar(d,sizeof(d),61);fillchar(f,sizeof(f),61); h:=1;r:=1;d[1]:=0;a[0]:=m+1;f[0]:=0; for i:=1 to n do begin  if i=8 then   h:=h;  while d[h]<b[i] do inc(h);  if d[h]<>b[i] then begin dec(h);d[h]:=b[i];update(h) end;  while (a[d[r]]<=a[i])and(r>h) do dec(r);  min:=ask(h,r-1);  if f[d[r]]+a[i]<min then min:=f[d[r]]+a[i];  f[i]:=min;inc(r);d[r]:=i;update(r-1) endend;procedure init;var i:longint;    flag:boolean;begin readln(n,m); fillchar(a,sizeof(a),0); flag:=false; for i:=1 to n do begin  read(a[i]);  if a[i]>m then flag:=true end; if flag then begin writeln(-1);exit end; origin; dp; writeln(f[n])end;begin initend.