题意
- 给你两个数n和k。有一棵完全二叉树,他的节点编号id是根节点为1,左孩子2i,右孩子2i+1。
- 你有一个数x,开始为0,从根节点出发,在一个节点上选择用x减去id或者加上id,然后可以去左孩子或者右孩子,每次加或减算一次操作,问你如何k次操作后使得x == n。
- 1<=n <= 10^9
- n <= 2^k <= 2^60
思路
- 我们看最左边的一枝,第m层的节点id = 2^(m-1),如果每次可以选择加或者不加该层节点,很明显就是一个2进制数,k层的可以凑出1~2^k-1的所有数,如果第k层我们选择,最左边节点右边的第一个节点,所有数都加,是不是就可以得到2^k了。所以题目这样设计的话,我们已经构造出解了。
- 但现在,题目是加或者减,这和刚才的问题有什么关联呢?保持其它节点的选择不变,看根节点,加或者不加,差为1,加或者减,差为2;类似的根节点的左孩子,加或者不加,差为2,加或者减差为4,这样我们就知道对于第i层的节点,加或不加,差为2^(i-1),加或者减差为2^i
- 由上面的结论,我们不难想到,保持第K层节点为加,第一层到第K-1层,选择加或者减,可以凑出1~2^k-1中数的一半,而且是从1开始每次加2的数,很显然就是可以凑出1~2^k-1中所有的奇数。
- 这样就简单了,当n是偶数时,只要把n-1,按上面的方法做,但第k层我们选择,最左边节点右边的第一个节点,这样所有的n都可以构造出来了。
吐槽
- 比赛时没有注意到n <= 2^k。。。回来分分钟就A了。。。真是tmd日了狗了。。。。
实现
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
typedef long long ll;
int main(){
int n,T,k;
cin>>T;
for (int t=1;t<=T;t++){
cin>>n>>k;
printf("Case #%d:\n",t);
ll N = n;
ll j = 1;
if ((n & 1) == 0)
N -= 1;
N >>= 1;
for (int i=1;i<k;i++){
cout<<j<<' ';
if (N & 1){
puts("+");
}
else{
puts("-");
}
j <<= 1;
N >>= 1;
}
if (n & 1)
cout << j << " +\n";
else
cout << j+(ll)1 << " +\n";
}
return 0;
}