每日一题,坚持使我强大
今日份快乐:codeforces 1348D 传送门
明天份快乐:codeforces 463C 传送门
题目大意
刚开始有一个细胞,质量为 1。每天白天,可以选择 x 个细胞进行分裂。一个质量为 m 的细胞可以分裂为两个质量为 m / 2 的细胞。每天晚上,所有的细胞质量都会加 1。
问:最短需要多少天,细胞的总质量为 n。不存在就输出 -1。
分析
我们直接结合一个样例来看:
当 n = 80 时,我们先根据贪心的思想,每天都让所有的细胞都分裂。
天数 | 开始 | 1 | 2 | 3 | 4 | 5 | 6 |
---|---|---|---|---|---|---|---|
当前细胞数 | 1 | 1 | 2 | 4 | 8 | 16 | 32 |
当前需要质量 | 80 | 79 | 77 | 73 | 65 | 49 | 17 |
分裂后细胞数 | 0 | 2 | 4 | 8 | 16 | 32 | 64 |
分裂后需要质量 | 79 | 77 | 73 | 65 | 49 | 17 | -47 |
观察上表,前五天的过程中,每天对所需质量的贡献都是这天时的细胞数。解释一下,比如说第三天时,我们刚开始有 4 个细胞,白天分裂为 8 个细胞,晚上所有细胞都质量加 1 ,则一共增加 8 个单位的质量,所需质量就减少 8 个单位。
但是可以发现,最后一天所需的质量为 17,不是 2 的次幂,不能被正好减去,这就很明显,我们需要在前五天中插入一天,让细胞数增值为 17 ,使得第六天需要的细胞数成为 2 的次幂,被正好消除
天数 | 开始 | 1 | 2 | 3 | 4 | 5 | 6 |
---|---|---|---|---|---|---|---|
当前细胞数 | 1 | 1 | 2 | 4 | 8 | 16 | 17 |
当前需要质量 | 80 | 79 | 77 | 73 | 65 | 49 | 32 |
分裂细胞数 | 0 | 1 | 2 | 4 | 8 | 1 | 15 |
分裂后细胞数 | 0 | 2 | 4 | 8 | 16 | 17 | 32 |
分裂后需要质量 | 79 | 77 | 73 | 65 | 49 | 32 | 0 |
我们在第五天的时候变动一下,让这天只分裂一个细胞,分裂完以后有 17 个细胞,正好为所需质量贡献 17 个单位的质量,第六天需要的质量正好为 32 是 2 的次幂可以被消除。
我们为什么选择在第五天呢?因为 16 < 17 < 32,我们把 16 增值为 32一天的操作,分为 16 到 17 再到 32两天的操作,这样的好处就是用最短的时间得到 17个单位质量的贡献值。这样也正好说明了,这个题不会出现无解的情况,因为无论留下的是多少,都可以被补全。(如果留下质量 n = 0 时,不用补。不为 0 时,一定会有一个 x 满足 2x < n < 2 x+1)。
所以,这个题的思路就是,我们让细胞尽可能的全部分裂,最后不够的质量,再找到一个合适的位置插入,如何实现看代码。
代码
#include <bits/stdc++.h>
#include <vector>
using namespace std;
int main() {
ios::sync_with_stdio(false);
int ncase;
cin >> ncase;
while(ncase--){
int n;
cin >> n;
vector<int>res;
for(int i = 1; i <= n; i <<= 1){ // 不断让所有的细胞分裂,直到所需质量小于当前细胞数
res.push_back(i); // res 中纪录的为每天最终的细胞数
n -= i; // 每次分裂对所需质量的贡献都为 i
}
if(n > 0){ // 如果所需质量不为 0
res.push_back(n); // 把这个质量插入到结果中
sort(res.begin(), res.end()); // 把 n 放到到满足 2^x < n < 2 ^ x+1 的位置
}
int len = res.size();
cout << len - 1 << endl; // 开始的 1 不用算
for(int i = 1; i < len; i++) cout << res[i] - res[i-1] << " "; // 相邻两天的细胞数的差就是要增值的数量
cout << endl;
}
return 0;
}
解释的不清楚的地方欢迎留言提出,同时欢迎大佬留言指正错误