hdu 6059 Kanade's trio

31 篇文章 0 订阅
2 篇文章 0 订阅

Problem

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

Reference

HDU 6059 Kanade’s trio (字典树, 2017 Multi-Univ Training Contest 3)
2017 Multi-University Training Contest 3 solutions BY 洪华敦(1004题)

Meaning

给一个序列 {An} ,问有多少个有序数对(i, j, k),满足:

  • i < j < k
  • AiAj<AjAk

Analysis

从第 2 个条件看到,Aj 在不等式两端都出现,应该是作为一个调节因子的存在,于是先找 Ai 和 Ak,通过它们的关系来“夹”出符合的 Aj。
看 Ai 和 Ak 的关系,考虑它们最高的不同的位(因为低位影响不及高位大,而相同位的用 Aj 调不出差异),假设是第 p 位,有两种情况:

  1. Ai[p]=1Ak[p]=0 。要满足第 2 条不等式,就要让 Aj[p]=1 ,根据异或性质,这样可以消掉 Ai 在 p 位这个 1,并且让 Aj 的 p 位变 1,于是满足不等式;
  2. Ai[p]=0Ak[p]=1 。这样 Aj[p] 就要为 1,分析同上;

于是看出 Ai 和 Aj 的一个关系: Ai[p]=Aj[p] (p 还是表示 Ai 和 Ak 最高的不同位)。
于是从前往后地把数插入到字典树中,把当前正在插入的数当做 Ak 来算边插入边算贡献。
上面说要考虑 Ai 和 Ak 最高的不同的位 p,那就把当前正在插入的位当做第 p 位,那么对于这个 Ak 来说,可选的 Ai 就是树上同一条路径下来的、在当前这一位才分岔出去的另一棵子树里记录的那些数。
然后找 Aj,由于 Ai 和 Aj 在第 p 位相同,所以上述 Ai 那棵子树里的数同时也能作为 Aj(这样的 Aj 高位与 Ak 相同);
Aj 还可以从别的子树里找,也就是高位与 Ak 不同的、但第 p 位还是与 Ai 相同的数。可以开个数组记录:num[i][j] = 第 i 位值为 j(0/1)的数的个数。但这些数未必符合 i < j 的条件。字典树结点里多记一个值,即参考博客里的ext,说的是如果把当前的节点当做 Ai(而不是 Ak)的话,那在它之前就已经插入的 p 位也为这个值的数,虽然 p 位值相同,但违反 i < j 的规定,把这些数的个数记下,在算贡献时减去。

Code

#include <cstdio>
#include <cstring>
using namespace std;
const int N = 500000, BIT = 30;

struct node
{
    int cnt;
    int sj; // smaller j,p 位相同但下标比较小的数的个数
    int nxt[2]; // child node
} trie[N*BIT+1];

int sz;
int dig[BIT]; // 拆下来的二进制位
int num[BIT][2]; // num[i][j]:已插入的第 i 位值为 j 的数的个数
long long ans;

void update(int p, int pre)
{
    // Ai 子树中任选两个分别当 Ai 和 Aj
    ans += (long long)trie[p].cnt * (trie[p].cnt - 1) >> 1;
    // Ai 子树上的只做 Ai,而在其它子树找 Aj,但要去除 i > j 的
    ans += (long long)trie[p].cnt * (pre - trie[p].cnt) - trie[p].sj;
}

void insert()
{
    for(int b = BIT - 1, p = 0, d; ~b; --b)
    {
        d = dig[b];
        if(!trie[p].nxt[d])
            trie[p].nxt[d] = sz++;
        if(trie[p].nxt[1-d])
            update(trie[p].nxt[1 - d], num[b][1 - d]);
        p = trie[p].nxt[d];
        ++trie[p].cnt;
        trie[p].sj += num[b][d] - trie[p].cnt;
    }
}

int main()
{
    int T;
    scanf("%d", &T);
    while(T--)
    {
        int n;
        scanf("%d", &n);
        memset(num, 0, sizeof num);
        memset(trie, 0, sizeof trie);
        sz = 1;
        ans = 0;
        for(int a; n--; )
        {
            scanf("%d", &a);
            for(int b = 0; b < BIT; ++b, a >>= 1)
            {
                ++num[b][a & 1];
                dig[b] = a & 1;
            }
            insert();
        }
        printf("%I64d\n", ans);
    }
    return 0;
}

题目的trio莫非就是在暗示trie

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值