2017-2018 ACM-ICPC, NEERC, Northern Subregional Contest D Dividing Marbles
题目大意:
给出一个$N(N <= 2^{22}$),$N$的二进制表示中1的个数不超过4. 一开始有一个集合$S = {N}$, 每次操作可以选择$n\in S \ (n > 1)$, 将$n$拆成两个正整数$n_1$和$n_2$,$n = n1 + n2$, 然后令$S = \{S \setminus n\} \cup \{n_1, n_2\}$. 问最少多少次操作使得$S = \{ 1 \}$.
题解:
考虑将这个过程倒过来,本质是让求一个最短的Brauer chain。考虑一个Brauer chain $a_0, a_1, \dots a_k$, 其中$a_0 = 1$, $ a_k = N$. 首先类似快速幂随便构造一下可以得到一个长度为$s+t-2$的Brauer chain,$s$ 是$N$二进制位数,$t$是二进制表示下1的个数。 比如$N = (1110)_2, s = 4, t = 3$, 可以如下构造:$\{(1)_2, (10)_2, (100)_2, (1000)_2, (1100)_2, (1110)_2\}$. 因此答案上界是$s+t-2$.
进一步挖掘一下性质:
$$s = \lfloor log_{2}{n} \rfloor + 1 , t \le 4$$
$$k \le s + t - 2 \le \lfloor log_{2}{n} \rfloor + 1 + 4 - 2 \le log_{2}{n} + 3$$
可以得到$ a_k \ge 2^{k - 3}$,根据Brauer chain的性质,必须有$a_{i - 1} >= \frac{a_i}{2}$. 从$a_k$倒着推回去可以推出任意$0 \le i \le k$, $a_i \ge 2^{i - 3}$.
爆搜所有满足这个性质且最后一项不超过$2^{22}$的Brauer chain。 发现本地大概要跑50s。
然后想到没必要让$k \le s + t - 2 $取等号,因为等号的情况可以直接构造。所以我们让爆搜的条件更加严格一点,$k < s + t - 2 $ 也就是说$ k \le s + t - 3$. 重新顺着刚才的思路推导一遍得到更强的条件$ a_i \ge 2^{i - 2}$。 然后爆搜就只要0.2s左右了。对于没有搜到的解,直接构造即可。
代码:
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 #define N 100010 5 typedef long long LL; 6 const int mod = 998244353; 7 const double EPS = 1e-12; 8 9 int a[30]; 10 vector<int> ans[(1 << 22) + 5]; 11 12 void DFS(int k) 13 { 14 if (k >= 2 && a[k] < (1 << (k - 2))) return; 15 if (k > 25) return; 16 17 if (__builtin_popcount(a[k]) <= 4 && (ans[a[k]].size() == 0 || k < ans[a[k]].size())) 18 { 19 ans[a[k]].clear(); 20 ans[a[k]] = vector<int>(a, a + k + 1); 21 } 22 for (int i = 0; i <= k; ++i) 23 { 24 a[k + 1] = a[k] + a[i]; 25 if (a[k + 1] > (1 << 22)) break; 26 DFS(k + 1); 27 } 28 } 29 30 int main() 31 { 32 freopen("dividing.in", "r", stdin); 33 freopen("dividing.out", "w", stdout); 34 35 a[0] = 1; 36 DFS(0); 37 for (int i = 1; i <= (1 << 22); ++i) 38 { 39 if (__builtin_popcount(i) > 4 || ans[i].size() > 0) continue; 40 41 int j; 42 for (j = 0; (1 << j) <= i; ++j) 43 ans[i].push_back(1 << j); 44 j--; 45 int now = 1 << j; 46 for (int k = 0; k < j; ++k) 47 { 48 if ((i >> k) & 1) 49 { 50 now |= 1 << k; 51 ans[i].push_back(now); 52 } 53 } 54 } 55 56 int T, d1, d2, d3, d4, n, k; 57 scanf("%d", &T); 58 while (T--) 59 { 60 scanf("%d %d %d %d", &d1, &d2, &d3, &d4); 61 n = (1 << d1) + (1 << d2) + (1 << d3) + (1 << d4); 62 63 printf("%d\n", k = ans[n].size() - 1); 64 65 for (int i = k; i >= 1; --i) 66 { 67 printf("%d %d %d\n", ans[n][i], ans[n][i - 1], ans[n][i] - ans[n][i - 1]); 68 } 69 } 70 71 72 return 0; 73 }