2017多校三 1004题 hdu 6059 Kanade's trio Trie树 计数

本文介绍了一种使用Trie树解决特殊元组(i,j,k)计数问题的方法,重点在于如何通过枚举a[j]来查找满足条件的a[i]和a[k],并详细解释了相关数据结构和算法细节。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目链接


题意:

Problem Description
Give you an array  A[1..n] ,you need to calculate how many tuples  (i,j,k)  satisfy that  (i<j<k)  and  ((A[i] xor A[j])<(A[j] xor A[k]))

There are T test cases.

1T20

1n5105

0A[i]<230


参考:

http://blog.youkuaiyun.com/u013944294/article/details/76577490 ——QQ小炫


看了很多题解...主流的做法是从左向右插入 a[k],对于 a[k] 的每一位,去看与它前面所有位都相同的而当前位不同的 a[i],即与 a[k] 的父亲的另一棵子树。只要 a[j] 的当前位与 a[i] 相同即可满足条件,所以再用一个 cnt[i][j] 记录第 i 位为 j (0/1) 的数字有多少个。另外,还需要处理的一个问题就是 i < j 的要求,这一点我没看太懂...

于是继续找,找到了上面那位博主的文章,觉得这种思路真的是很巧。


主要想法是枚举 a[j], 看有多少个 a[i] 及 a[k] 满足条件。

先将数组中所有数字插入到 trie 树中,此时记录下 sz[rt][1][bit],意为以 rt 为根的子树中下一位为 bit 的有多少个,中间一维的含义之后再解释。

然后再从左向右模拟一遍插入 a[j] 的过程,怎么模拟呢?

这里也引入 cnt 数组,但含义与上面那种不同。这里的 cnt[i][j] 意为更高位的前缀都相同,第 i 位左边为 j ,右边为 j ^ 1 的数对个数。显然,由其含义可知,cnt 累和即为答案。

对于 a[j] 的第 i 位,当我们把 a[j] 向左移(即划入左边那块时),

若其为 0,左 0 右 1 的个数,即 cnt[i][0] 的个数会增加右边第 i 位为 1 的数的个数,即 sz[rt][1][1]

 左 1 右 0 的个数,会减去左边左边第 i 位为 1 的个数,即 cnt[i][1] -= sz[rt][0][1]

若其为 1,左 0 右 1 的个数,会减少左边第 i 位为 0 的个数,即 cnt[i][0] -= sz[rt][0][0]

 左 1 右 0 的个数,会加上右边第 i 位为 0 的个数,即 cnt[i][1] += sz[rt][1][0]

总结一下 cnt[i][bit] += sz[rt][1][bit ^ 1];

cnt[i][bit ^ 1] -= sz[rt][0][bit ^ 1].


这样,每次进入循环时加上当前的数字作为 a[j] 时的种数,即 cnt[j][bit],然后将 a[j] 移到左边为下一次做准备。

对原Po佩服得五体投地...。


Code:

#include <bits/stdc++.h>
#define maxn 500010
typedef long long LL;
LL cnt[31][2];
int trie[maxn * 31][2], tot, a[500010], sz[maxn * 31][2][2];
void ins(int x) {
    int rt = 0;
    for (int i = 29; i >= 0; --i) {
        int bit = (x >> i) & 1;
        if (!trie[rt][bit]) trie[rt][bit] = ++tot;
        ++sz[rt][1][bit];
        rt = trie[rt][bit];
    }
}
LL query(int x) {
    int rt = 0;
    LL ans = 0;
    for (int i = 29; i >= 0; --i) {
        int bit = (x >> i) & 1;
        ans += cnt[i][bit];
        cnt[i][bit] += sz[rt][1][bit ^ 1];
        cnt[i][bit ^ 1] -= sz[rt][0][bit ^ 1];
//        printf("%d : %lld %lld\n", i, cnt[i][0], cnt[i][1]);
        --sz[rt][1][bit];
        ++sz[rt][0][bit];
        rt = trie[rt][bit];
    }
    return ans;
}
void work() {
    int n; tot = 0;
    memset(cnt, 0, sizeof(cnt));
    memset(trie, 0, sizeof(trie));
    memset(sz, 0, sizeof(sz));
    scanf("%d", &n);
    for (int i = 0; i < n; ++i) {
        scanf("%d", &a[i]);
        ins(a[i]);
    }
    LL ans = 0;
    for (int i = 0; i < n; ++i) ans += query(a[i]);
    printf("%lld\n", ans);
}
int main() {
    int T;
    scanf("%d", &T);
    while (T--) work();
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值