2017第三次多校联合HDU6059

本文介绍了一种使用字典树解决特定三元组计数问题的方法。该问题要求找出序列中满足特定异或条件的三元组数量。通过构建字典树并运用巧妙的计数策略,文章详细解释了如何高效地解决这个问题。

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

题目

看了无数人的博客,冥思苦想终于搞明白了,都是泪啊。
题意:给你一个序列 a ,然后让你计算有多少个三元组(i,j,
k),满足i<j < k且((A[i] xor A[j])<(A[j] xor A[k]))。

题解:字典树。
用字典树处理前k-1个数,当前是第k个数。

显然,我们要寻找的i,j需要满足的条件可以转化为: AiAk 的最高不同位t,而 Aj 的第t位与 Ai 的第t位相同。这样异或的结果就满足了要求。因为 AiAk 第t位之前的一定是全部相同的,所以不论 Aj 选取什么数,都不会对更高位的比较有影响。
举个例子:当 it=0,kt=1 ,此时 jt 必须满足等于0,才能使异或结果满足条件,并且j的更高位是无所谓的。
所以利用数组 cnt[31][2] 记录已插入的数字中每一位的0/1有多少个,num数组表示第K个数的每一位。在插入第K个数的时候,统计最高不同位,插入的第t位是 num[t] ,且他就是最高不同位,那它的同父节点的 1num[t] 为根节点的树中所有节点均可成为i来寻找j,假设这个 1num[t] 位根节点的树中有3个数字,就可以利用 C (23)来进行选择(因为这三个数里一定有插入的先后顺序的,只要让小的成为i,大的成为j即可)。
但是对于插入的第t位来说,j不一定只在它的同父节点的子树里,因为j只需满足 jt it 相同即可,所以它可以在第t位所在的这一层中与 it 相同的子树中就可以。但是其中也会有不合法的情况,因为i是必须小于j的,所以我们添加ext这个变量来记录不合法的情况。
对于ext这个变量,我们在处理完一个点的时候,我们可以将这个点作为i,来寻找有多少个点可以与这个点形成i,j的关系,记录下来,最后一起减去。(因为我们插入的是第K个数,所以它一定是大于所有已经插入的数的,所以这里找到的情况都是不合法的)。
我就卡在这个ext这里卡了好久。。。难过。。。。

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn=5e5+10;
int cnt[31][2],a[maxn],num[30];
struct bbq{
    int nxt[2];
    int cnt,ext;//cnt记录这个点被经过了几次。
};
bbq T[maxn*31];
int tsize;
long long ans;
void calc(int tmp,int cnt)
{
    ans+=T[tmp].cnt*1ll*(T[tmp].cnt-1)/2;
    ans+=(cnt-T[tmp].cnt)*1ll*T[tmp].cnt-T[tmp].ext;
}
void in()
{
    int tmp=0;
    for(int i=0;i<30;i++)
    {
        if(!T[tmp].nxt[num[i]])
            T[tmp].nxt[num[i]]=++tsize;
        if(T[tmp].nxt[1-num[i]])
        {
            calc(T[tmp].nxt[1-num[i]],cnt[i][1-num[i]]);
        }
        tmp=T[tmp].nxt[num[i]];
        T[tmp].cnt++;
        T[tmp].ext+=cnt[i][num[i]]-T[tmp].cnt;
    }
}
int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        int n;
        scanf("%d",&n);
        memset(num,0,sizeof num);
        memset(cnt,0,sizeof cnt);
        memset(T,0,sizeof T);
        ans=tsize=0;
        for(int i=0;i<n;i++)
        {
            scanf("%d",&a[i]);
            int temp=a[i];
            for(int j=29;j>=0;j--)
            {
                num[j]=temp%2;
                cnt[j][temp%2]++;
                temp/=2;
            }
            in();
        }
        printf("%lld\n",ans);
    }
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值