开始以为是DP然后用了一个时间复杂度数量级为(10^7)的DP来做,结果case 35超时,后来发现就是一个逻辑分析题,就知道有贪心策略的,250分的题怎么可能是DP呢。
分析
首先考虑一个数的情况,任意一个数a的形成可以通过如下构造来表示:
(egin{equation}egin{split}a&=((((0+n_0) imes2+n_1) imes2+n_2) imes2+...+n_{k-1}) imes2+n_{k}\&=sum_{i=0}^{k} n_i imes2^{k-i} (n^i in N) end{split}end{equation})
将连续的(+1)操作看成(+n)不难得出以上变换,因此a的变换可以看成一个特殊的二进制数,跟一般的二进制数不同之处在于每位的数字可以超过1。
将a重新表示成:
(a=overline{n_0n_1n_2...n_k}quad(例: 10=overline{210}=overline{0106}=overline{1010}))
注意该种表示法的以下特点:
1. a的前缀零意味着开始一直在做Doubling操作
2. 各种结构可以通过进位(当前位-2,高一位+1)和借位(当前位+2,高一位-1)操作相互变换
3. k就是Doubling操作的次数
从0变换到a所需的操作数s等于:
(s(overline{n_0n_1n_2...n_k})=k+sum_{i=0}^{k}n_iquad(overline{n_0n_1n_2...n_k}=a))
当a表示为一般二进制数且没有多余前缀零的时候可令s最小。注意到任何一次进位操作可以使s变小或保持不变,所以尽可能的进位最后得出的数形就是一般二进制数。
现在考虑多个数(a_i)的情况,每个数可以转变为各自的特殊二进制数,但是由于Doubling操作的存在使得它们的位数必须相等(前缀零也要占位,如果有的话)。
令Doubling操作的次数为t,若已知t,除去总的一起做的Doubling操作,最优解一定是限制条件下每个数单独的最优解之和,单独最优解(s(a,t))可以是将a尽可能进位的结果,即:
( egin{equation}egin{split} s(a,t)_{min} &= s(overline{n_0n_1...n_t})_{min}quad&(a=overline{n_0n_1...n_t}) \ &= s(overline{n_0n_1...n_t})-tquad&(igeq1, n_i < 2)(注意t要被省去) \ &= sum_{i=0}^{t}n_i \ &= a // 2^t + sum_{i=1}^{t}n_i quad&(n_0=a // 2^t)end{split}end{equation} )
最后,枚举t计算所有可能情况下的最小值,总步骤数tot:
(tot(t)={t+sum_{i}s(a_i, t)_{min}|tin[0,10]})
(tot_{min}=min(tot(t)))
优化
进一步可以证明当 (t+1) 为所有数的二进制位数的最大值的时候,(tot) 取最小值 (tot_{min}),证明如下:
令 (len(a)) 表示数a的一般二进制数的位数减一,若 (t < max(len(a_i))) 则:
(egin{equation}egin{split}tot(t+1)-tot(t)&=1+sum_{i}^{len(a_i)>t}(s(a_i, t+1)-s(a_i, t)) \ &=1+sum_{i}^{len(a_i)>t}(a_i//2^{t+1}+a_1-a_i//2^t) quad &(a_1是t+1位的情况) \ &=(1+sum_{i}^{len(a_i)>t}a_1)+(sum_{i}^{len(a_i)>t}(-a_i//2^{t+1})) \ &leq 1-a_k//2^{t+1} leq 0 quad &(len(a_k)=max(len(a_i))) end{split}end{equation})
所以有 ( tot(t+1) leq tot(t))
则:
令 (sum(a)) 为a的一般二进制数的各位数字之和:
(tot_{min}=max(len(a_i)) + sum_{i}sum(a_i))
注意这道题如果没有考虑到前缀零也可以占位的情况就会直接得出「t可以取所有数的一般二进制数的位数的最小值」的错误结论,好在题目里有测试点可以测出这个bug,所以不会被无情challenge

class IncrementAndDoubling: def getMin(self, a): x = max(a) maxk = 0 while True: maxk += 1 x = x // 2 if x == 0: break s = maxk - 1 for x in a: for i in range(maxk): s += x >> i & 1 return s # test o = IncrementAndDoubling() # test 0 assert(o.getMin((0,)) == 0) # test case assert(o.getMin((2,1)) == 3) assert(o.getMin((16,16,16)) == 7) assert(o.getMin((100,)) == 9) assert(o.getMin((0, 0, 1, 0, 1)) == 2) assert(o.getMin((123, 234, 345, 456, 567, 789)) == 40) assert(o.getMin((7,5,8,1,8,6,6,5,3,5,5,2,8,9,9,4,6,9,4,4,1,9,9,2,8,4,7,4,8,8,6,3,9,4,3,4,5,1,9,8,3,8,3,7,9,3,8,4,4,7)) == 84) print('ok')