Binary Tree【2015上海现场赛】【状态压缩思维】

题意:给了你一棵有K层的完全二叉树,问你要做怎样的走法可以刚好搜集到N个碎片,每个节点有个对用的权值,我们如果要走这个节点,就必须要“+”它的权值或者“-”它的权值,经过计算后,我们输出所走的边以及对应的点所对权值的“+”or“-”。

题目给出的数据中有一个细节,N<=2^K<=2^60,前面的N与K之间的关系确实有用,不然,假如N=11,与K=3,是可行解,就是对应加上{1, 3, 7}即可,但是如果题意变成这样就难解了,好在题目没有刁难我们,虽然当时粗心并没有看到这组数据,在瞎JB乱推,庆幸时间恰好。

 

思路

  题目其实并不是很难,推了个想法:我们首先看到有2层的树:

1

2 3

我们如果走左端,能走到的是{1=2-1; 3=1+2};走右端能走到的是{2=3-1; 4=1+3}

看到三层的树:

1

2

4 5 

我们之所以推到5就停下来,是因为如果加到5就能完成“8”的和,所以已经足够(还有些细节,将在后面讲到)。走左能到{7, 5, 3, 1};走右能到{8, 6, 4, 2},确实不难发现,它把左右恰分成了奇偶组合。那么问题来了,为什么推到这就够了呢?其他的步骤为什么不做总结呢?会发现,在往后面的数,会出现自己本身不能达到而缺少值的现象,譬如举例到达6号节点,所能到达的值有{10, 8, 4, 2}唯独自己是到达不了的,这样的节点还是舍去来的方便,且已经满足了题目。

最后看一层四层的树,我们就能推出规律:

1

2

4

8 9

我们到达左边“8”所能得到的值有{15, 13, 11, 9, 7, 5, 3, 1};右边所能到达的值有{16, 14, 12, 10, 8, 6, 4, 2},我们如果把“15”、“16”分别作为两种方法的极限值,那么其他值剪去的数有恰好有些规律,譬如看左边,13=15-2,变化的是“1”这号节点的正负问题;11=15-4,变化的是“2”这号节点的正负;9=15-6变化的是“1”和“2”节点的正负......。我们可以发现了个规律,就是它的改变序位是否就正是二进制上的不断的加所一一对应的位数,譬如0001指的是“第一位”改变正负,0101指的是“第三位、第一位”改变正负,这样的规律显然是成立的。

总结可得

  • 对于M,我们可以将其与N的二进制最高“1”为所在的数位比较,取小;
  • 我们接下来考虑N的奇偶,若为奇数则走左路,为偶数走右路;
  • 最后根据N与极限位的值的差,用比特数来判断输出的是“-”还是“+”。

 

完整代码

#include <iostream>
#include <cstdio>
#include <cmath>
#include <string>
#include <cstring>
#include <algorithm>
#include <limits>
#include <vector>
#include <stack>
#include <queue>
#include <set>
#include <map>
#define lowbit(x) ( x&(-x) )
#define pi 3.141592653589793
#define e 2.718281828459045
using namespace std;
typedef long long ll;
ll N, M;
int main()
{
    int T;  scanf("%d", &T);
    for(int Cas=1; Cas<=T; Cas++)
    {
        scanf("%lld%lld", &N, &M);
        printf("Case #%d:\n", Cas);
        for(int i=60; i>=0; i--)
        {
            if(N & ((ll)1<<i))
            {
                M=i+1;
                break;
            }
        }
        if(N & 1)
        {
            ll tot=(1<<(M))-1;
            ll step=(tot-N)>>1;
            for(int i=1; i<=M; i++)
            {
                int state=(1&( step>>(i-1) ) );
                if(state) printf("%lld -\n", ((ll)1<<(i-1)) );
                else printf("%lld +\n", ((ll)1<<(i-1)) );
            }
        }
        else
        {
            ll tot=(1<<(M));
            ll step=(tot-N)>>1;
            for(int i=1; i<=M; i++)
            {
                int state=(1&( step>>(i-1) ) );
                if(i==M)
                {
                    if(state) printf("%lld -\n", ((ll)1<<(i-1))+1 );
                    else printf("%lld +\n", ((ll)1<<(i-1))+1 );
                    break;
                }
                if(state) printf("%lld -\n", ((ll)1<<(i-1)) );
                else printf("%lld +\n", ((ll)1<<(i-1)) );
            }
        }
    }
    return 0;
}

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Wuliwuliii

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值