题目链接
做法
由于题目中要把每段的和或起来,所以我们考虑一位一位考虑。
很自然的想到用$f_{i,j}$表示前$i$个数分为$j$段的最小美观值,转移的话就枚举$t..i$为一段,$f_{i,j}=Min(f_{i,j},f_{t-1,j-1}|(sum_i-sum_{t-1}))$,用$sum_i$表示到$i$为止的前缀和。
最后取$f_{n,a..b}$的最小值。
但是这个$dp$会有问题,由于或的特性,只要你分出的这些段中有一段在二进制这位上为$1$,这个二进制位就会对答案产生贡献。
举个例子。
不管怎么分段,最后答案中二进制第三位一定为1,而你在上面的$dp$中,可能会认为$010$是比$100$更优的,导致最后答案中二进制的第二位也产生了贡献,而这实际上是可以避免的。
那么我们怎么做呢,其实上面叉掉$dp$的过程已经给我们提示了。
我们从二进制的高位向低位枚举,如果最后答案这一位上可以是$0$,那么我们一定让这一位为$0$,当然前提是不会对前面更高的二进制位产生影响。
对于$n\le 100$的$subtask$,我们先枚举目前二进制位从大到小做到第$i$位,用$f_{j,k}$表示在满足前$i-1$位的前提下,将前$j$个数分成$k$段能否使得二进制的第$i$位上为$0$,转移与上面的$dp$类似,枚举这一段从$t$开始,令$s=sum[j]-sum[t-1]$,$ans$表示前$i-1$位的答案。
$s$要满足下面几个性质,我们认为这一段是合法的
(1) $s$的前$i-1$位必须是$ans$的子集
(2) $s$的第$i-1$位为$0$
如果最后$f_{n,a..b}$有$dp$值为$1$的状态,说明$ans$这一位可以放$0$,否则必须放$1$
复杂度为$O(63*n^3)$
无法通过最后一个$n\le 2000$的$subtask$
但是注意到最后一个$subtask$中,$a=1$
同样还是从高到低枚举二进制位,但改变$dp$状态,用$g_i$表示将前$i$个数分成符合条件最少需要几段。
转移与上面的$dp$一样
最后判一下是否$g_n\le b$就能判断$ans$这一位上能不能放$0$了。
复杂度$O(63*n^2)$
你可能会问为什么下面这种做法为什么只有$a=1$时可行。
那是因为这个$dp$没有单调性,也就是说你分成$i$段可行并不代表所有大于$i$段的分法均可性,所以取最小的不一定正确。
把两个$dp$拼起来就能通过此题
代码
1 |
|