题意 : 给出一个数 n ,要求你用 k 个二的幂来组成这个数,要求输出这 k 个二的幂的指数,如果有多解情况则优先输出最大指数最小的那一个且要求按字典序输出,不存在则输出 No
分析 :
先来说一个结论对于一个二的幂例如 2^n 我们可以将其拆成 2^(n-1) + 2^(n-1)
那么对于题目所给出的数 n 我们可以先将其拆成二进制形式,如果当前二进制表示法中 1 的个数已经超过 k 则应该是输出 No
也就是说无论怎么转化都不可能有用更少的二的幂的个数来表示这个数,只能更多,也就是利用上面一拆多的结论
那么接下来就是用上述结论去一位位拆,为了保证拆出来的是符合题目要求的,按如下方式拆
首先为了保证最大的二的幂指数尽量小,我们始终从最高位拆
但是为了不超过 k 这个限制,当 2*最高位的二的幂的数量已经大于当前需要补充的数时候
这时候我们无法使得最大的二的幂被拆掉,所以为了保证字典序最小,我们此时从最低位开始拆
举个例子来说就是假设当前 n = 1010 (这里是二进制表示)、k = 4
那么一开始 n = 2^3 + 2^1 还需要补充 2 个二的幂
那么从最高位开始拆,变成 n = 2^2 + 2^2 + 2^1
此时还差 1 个二的幂,不过这个时候不能去拆最大的二的幂
应该拆 2^1 才能保证字典序
最后 n = 2^2 + 2^2 + 2^0 + 2^0
#include<bits/stdc++.h> #define LL long long using namespace std; const int maxn = 1e5 + 10; int k, cnt, lowbit, idx, arr[maxn]; LL n; int main(void) { lowbit = -1; scanf("%I64d %d", &n, &k); while(n){/// 先把 n 拆成二进制数 if(n & 1){ cnt++;/// n 的二进制表示法中 1 的数量 arr[idx]++;/// 记录当前二进制 1 所代表到底是哪个幂,例如 arr[2] => 2^2 if(lowbit == -1) lowbit = idx; } n >>= 1; idx++;/// n 的二进制表示法中有效位长 } lowbit = (idx-1)-lowbit;/// 计算出 n 的二进制表示法中最低位的那个 1 是第几位 std::reverse(arr, arr+idx); //----------Debug------------------- // for(int i=0; i<idx; i++) // printf("%d ", arr[i]); // puts(""); // printf("%d %d ", idx, lowbit); //----------Debug------------------- if(cnt > k){/// No 的情况 puts("No"); return 0; } for(int i=0 ;; i++){///从最高位开始进行拆分操作 if(cnt == k) break; if(k - cnt < arr[i]) break; cnt += arr[i]; arr[i+1] += arr[i]<<1; arr[i] = 0; lowbit = max(lowbit, i+1); } //------------Wrong!!!----------------------- // while(cnt != k){ // if(arr[lowbit] >= k - cnt){ // arr[lowbit] -= k-cnt; // arr[lowbit+1] += (k-cnt)<<1; // cnt = k; // }else{ // arr[lowbit+1] += arr[lowbit]<<1; // cnt += arr[lowbit]; // arr[lowbit] = 0; // } // lowbit += 1; // } //------------Wrong!!!----------------------- while(cnt != k){///Greedy!!! cnt++; arr[lowbit]--; arr[++lowbit] += 2; } puts("Yes"); for(int i=0; i<=lowbit; i++){ while(arr[i]){ arr[i]--; printf("%d ", idx-1-i); } }puts(""); return 0; }