最小M段和 O(nlogn)快速算法

先声明,这算法我还没弄出来

 

 

 

《数列分割》解题报告

[题目描述]

给出一个长为N的整数数列(N≤15000),要求求出最小的M,使得存在一种将数列分成恰好K份的方案(每份是数列上连续的一段,且不得为空),每份的数字之和不大于M

题目来源:Viet Nam Olympiad in Informatic 2005, Day I

[算法分析]

此题又是典型的要求最大值尽量小的形式,对于这样的问题,往往可以使用基于二分的算法解决。对于此题,则是二分枚举M的值,再判断对于这个M是否存在划分方案并缩小范围。

此题的难度在于,数列中可能存在负数,那么当数列的一个前缀的数字和已经大于M时,更后面的前缀和仍然有可能小于M(因为负数)。另外,对于一个特定的M,如果数列存在划分为K-1份的方案,并不一定存在划分为K份的方案,例如数列 -1 -1 -2,对于M-2,存在划分为2份的方案,却不存在划分为3份的方案。这样,一些常用的简单算法将不再适用。

在找不到有效算法时,先来看看朴素的算法。对于是否存在一种恰好K份的划分方案,使得每一份的数字和不大于M”这一问题,很容易设计出这样一个动态规划算法:

S[i]表示原数列前i个数构成的子序列,Sum[i]表示S[i]中所有数字之和。定义f[i][j]表示是否存在将S[i]划分为恰好j份的方案,其中每一份的数字之和不大于Mf[i][j]都为truefalse

对于j0f[i][j]当且仅当i0时为true,其余为false

对于i0f[i][j]当且仅当j0时为true,其余为false

对于j>0i>0f[i][j]f[i1][j-1] or f[i2][j-1] or … or f[il][j-1]ix是所有在[0i-1]范围内且满足Sum[i]-Sum[ix]≤M的整数。

这个算法的时间复杂度高达ON3),对于本题数据范围无法承受。但是容易发现,这个模型中状态形式简单(布尔类型),转移方程也比较特殊,可能会存在一些内在的规律。那么现在我们来深入研究这个动态规划模型。

转移方程的第一个特殊之处在于,对于同样的i不同的j,方程中的i1…il的值都是相同的

 

正在处理的是i7一行,对于这一行中的每个格子(状态),它的值由前面行中和它同色格子的值进行or运算得到,由于转移方程的特殊形式,就成了上图那样的形态。如果将每一行看成一个布尔向量,那么计算第i行实际上就是先找出i1…il,再将这l个向量相加(此处是or运算),并向右位移一位,最高位舍弃(实际上一定为false),最低位补0,就得到了第i个向量。

不过这一认识暂时不能帮助降低算法的时间复杂度,还需要更多的东西。回到转移方程中,观察i1…il这个集合,它看似是散乱的,其实不然,它是ixSum[i]-Sum[ix]≤M的集合,即Sum[i]-M≤Sum[ix],如果将0i-1行按照Sum值从大到小排序,那么i1…il对应的实际上就是前l

 

那么进行到第i行时,可以在0i-1行使用二分查找找出转移所涉及的行,将他们or起来、右位移得到第i行的结果,再将第i行插入到合适的位置,保持0i行按Sum降序排列。

上面的描述涉及的操作有:二分查找一个位置、将元素(布尔向量)插入到某个空的位置、求某个位置前所有向量or起来的值。这是典型的可以使用离散化+树状数组解决的问题,但是,由于操作的元素是向量,基本操作就有ON)的代价,导致算法的时间复杂度为ON2 log N),空间复杂度为ON2),对于本题的数据范围无法承受。

回到前面的算法分析,对于第i行,它的值是通过前面连续的lor起来再进行一次位移得到的,如果将得到的值和前面l行再or起来,相当于把一个向量右位移一个单位再和自己or起来,给人直观的感觉是,向量里的true不会变得更零散,而有可能变得更连续,特别地,如果这个向量里的true本来就是连续的一段,右位移再or的结果仍然是连续的。另外,第一行是连续的(只有1true),这启发我们可以使用数学归纳法来得到一些东西。稍作分析就能得到,在算法进行的任意时刻,任意一个向量里的true都是连续的,且按Sum值倒排后前任意多个连续向量or起来的结果里的true也是连续的。

有了这个结论,我们就可以使用一个整数对来描述一个向量:(XY)表示向量中从XY这一段为trueor运算和位移运算都可以简单地实现:

X1Y1orX2Y2)=(minX1X2),maxY1Y2))

XY>>1=(X1Y1

将这样的表示法用到前一个算法中,就得到了时间复杂度ON log N)、空间复杂度ON)的可行算法。

[总结]

本题具有这样低复杂度的算法的关键原因在于,每个向量里的true都是连续的,跳出上文的语境,就得到了一个数学命题:

要求将一个数列划分为每份数字之和不大于M的若干份,如果同时存在划分为A份和B份的方案,那么也存在划分为[AB]中的每一个份数的方案。

上文实际上证明了这个结论,但是通过了朴素的动态规划算法搭桥。惭愧的是我并没有找到使用纯数学工具进行证明的方法,希望各位大牛不吝赐教。

 

 

 

 

感谢xqz提供的程序

{$inline on} program xqz; const maxn=20005; oo=1 shl 30; var i,j,k,m,n,ll,rr,mid,t,k1,k2:longint; a,s,c1,c2,f,g:array[0..maxn] of longint; procedure sort(ll,rr:longint); var i,j:longint; begin i:=ll; j:=rr; t:=a[random(rr-ll+1)+ll]; while i<=j do begin while a[i]>t do inc(i); while a[j]ll then sort(ll,j); if i=x then ll:=mid else rr:=mid-1; end; get:=ll; end; procedure find(i:longint); begin while i>0 do begin if c1[i]k2 then k2:=c2[i]; i:=i-i and(-i); end; end; procedure add(i,k:longint); begin while i<=n+1 do begin if f[k]c2[i] then c2[i]:=g[k]; i:=i+i and(-i); end; end; begin assign(input,'candy.in'); reset(input); assign(output,'candy.out'); rewrite(output); read(n,m); for i:=1 to n do begin read(s[i]); if s[i]>0 then rr:=rr+s[i] else ll:=ll+s[i]; s[i]:=s[i-1]+s[i]; a[i]:=s[i]; end; a[n+1]:=0; sort(1,n+1); a[0]:=oo; while ll-1 then inc(k2); f[i]:=k1; g[i]:=k2; add(get(s[i]),i); end; if (f[n]<=m)and(g[n]>=m) then rr:=mid else ll:=mid+1; end; writeln(ll); close(input); close(output); end.  

 

 

 

马上我就自己打一边然后发出来,不会让大家看这么丑的程序的......

 

你可能感兴趣的:(最小M段和 O(nlogn)快速算法)