hdu 6121 Build a tree

31 篇文章 1 订阅

Problem

acm.hdu.edu.cn/showproblem.php?pid=6121

Meaning

一棵 n 个点的完全 k 叉树,结点标号从 0 到 n - 1,求以每一棵子树的大小的异或和。

Analysis

一层层地统计答案。
tree
找到一个特殊点:n - 1。以从它到树根的路径上的每一个点 p 为界,把树在该层的点劈成两半:左边是满 k 叉树的树根,右边是比左边的少了一层的满 k 叉树的树根,都很好算。
而 p 为根的子树,叶子层有可能是残缺的,除了叶子层外,也是一棵满 k 叉树。只要找到以 p 为根的子树中最左下角的后代结点编号,用 n - 1 一减,就得出子树 p 的叶子数。
要特判 k = 1 退化成链的情况(谜之打表找规律),照做应该会超时?
要注意各种爆 long long,比如:最后一层单独做、换乘法为除法…

Code

#include <cstdio>
using namespace std;
const int N = 1000000;

long long fast_pow(long long a, int b)
{
    long long res = 1;
    for( ; b; b >>= 1, a *= a)
        if(b & 1)
            res *= a;
    return res;
}

// 满 k 叉树,前 n 层总结点数
long long nNode(long long k, int n)
{
    return (fast_pow(k, n) - 1) / (k - 1);
}

long long table[100];

// table[i] = 满 k 叉树前 i 层总结点数
// 减少 nNode() 的调用(快速幂的次数)
// 虽然后来发现超时的原因并不在这
void da_biao(long long k, int d)
{
    for(int i = 0; i <= d; ++i)
        table[i] = nNode(k, i);
}

int main()
{
    int T;
    scanf("%d", &T);
    while(T--)
    {
        long long n, k, ans = 0;
        scanf("%I64d%I64d", &n, &k);
        // 特判 k = 1
        if(k == 1)
        {
            int rest = n & 3; // n % 4
            if(!rest)
                ans = n;
            else if(rest == 1)
                ans = 1;
            else if(rest == 2)
                ans = n + 1;
            else
                ans = 0;
            printf("%I64d\n", ans);
            continue;
        }
        // 求树的高度
        // 根的高度设为 1
        int depth = 1;
        for(long long m = n - 1; m > 0; ++depth)
            m = (m - 1) / k;

        da_biao(k, depth + 2);

        // 整棵树
        ans = n;
        // 最底层单独做
        ans ^= (n - table[depth - 1]) & 1;

        --depth;
        long long p = (n - 2) / k; // [(n - 1) - 1] / k,倒数第 2 层开始
        long long lid, rid, lnum, rnum, lch;
        for(int d = 2; p > 0; p = (p - 1) / k, ++d, --depth)
        {
            // 当前层最左边结点的标号
            lid = table[depth - 1];
            // 当前层最右边结点的标号
            rid = table[depth] - 1;
            // 左边的子树(满的)的大小
            lnum = table[d];
            // 右边的子树(少一层,但也是满的)的大小
            rnum = table[d - 1];

            if((p - lid) & 1)
                ans ^= lnum;
            if((rid - p) & 1)
                ans ^= rnum;

            lch = p; // p 为根的子数最左小角的后代结点
            while(lch <= (n - 2) / k) // lch * k + 1 <= n - 1
                lch = lch * k + 1;
            ans ^= table[d - 1] + n - lch;
        }
        printf("%I64d\n", ans);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值