SRM596 DIV1 250

开始以为是DP然后用了一个时间复杂度数量级为\(10^7\)的DP来做,结果case 35超时,后来发现就是一个逻辑分析题,就知道有贪心策略的,250分的题怎么可能是DP呢。

分析

首先考虑一个数的情况,任意一个数a的形成可以通过如下构造来表示:

\(\begin{equation}\begin{split}a&=((((0+n_0)\times2+n_1)\times2+n_2)\times2+...+n_{k-1})\times2+n_{k}\\&=\sum_{i=0}^{k} n_i\times2^{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_i\quad(\overline{n_0n_1n_2...n_k}=a)\)

当a表示为一般二进制数且没有多余前缀零的时候可令s最小。注意到任何一次进位操作可以使s变小或保持不变,所以尽可能的进位最后得出的数形就是一般二进制数。

现在考虑多个数\(a_i\)的情况,每个数可以转变为各自的特殊二进制数,但是由于Doubling操作的存在使得它们的位数必须相等(前缀零也要占位,如果有的话)。

令Doubling操作的次数为t,若已知t,除去总的一起做的Doubling操作,最优解一定是限制条件下每个数单独的最优解之和,单独最优解\(s(a,t)\)可以是将a尽可能进位的结果,即:

\( \begin{equation}\begin{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})-t\quad&(i\geq1, 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}|t\in[0,10]\}\)

\(tot_{min}=min(tot(t))\)

优化

进一步可以证明当 \(t+1\) 为所有数的二进制位数的最大值的时候,\(tot\) 取最小值 \(tot_{min}\),证明如下:

令 \(len(a)\) 表示数a的一般二进制数的位数减一,若 \(t < max(len(a_i))\) 则:

\(\begin{equation}\begin{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')
View Code

 

posted @ 2013-11-19 10:11  valaxy  阅读(190)  评论(0编辑  收藏  举报