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 }

 

posted @ 2018-08-16 19:30  lzw4896s  阅读(479)  评论(0编辑  收藏  举报