HDU 6059 Kanade's trio(字典树)

Description
给出一个长度为n的序列A[1]~A[n],求满足i < j < k且(A[i]^A[j])<(A[j]^A[k])的三元组(i,j,k)个数
Input
第一行一整数T表示用例组数,每组用例首先输入一整数n表示序列长度,之后输入n个整数A[1]~A[n] (1<=T<=20,1<=sum{n}<=5e5,0<=A[i]<=2^30)
Output
对于每组用例,输出满足条件的三元组个数
Sample Input
1
5
1 2 3 4 5
Sample Output
6
Solution
对于任一对A[i],A[k],如果有满足条件的A[j]存在,假设A[i],A[k]在第x位首次出现不同,那么对于A[j],其前x-1位随意,但第x位需要和A[i]相同,考虑枚举A[i],找合法的A[j]和A[k],因为要i < j < k,故把所有数字按编号从大到小插入到字典树中,令cnt[i][j][k]为前i个数中在第j位是k的个数,sum[p]为经过字典树中p节点的数作为A[k]时,在当前层满足条件的A[j]个数,即A[j]的编号需要小于A[k]且在p节点所处层A[j]的值与A[k]的值不同,num[p]为经过字典树p节点的数的个数,每次插入的数作为A[i]时,考虑其在字典树第j层(即第j位)产生的答案,假设这个数在第j层值为v,所处节点为l,对偶节点为r,那么在当前层与A[i]不同的A[k]有num[r]个,sum[r]表示以经过r的数作为A[k]满足条件的A[j]的数量,但是这些A[j]中有不合法值即为那些编号小于等于i的A[j],这些不合法值共有cnt[i][j][v]个,与num[r]个A[k]组合产生cnt[i][j][v]*num[r]种不合法情况,进而sum[r]-cnt[i][j][v]*num[r]为当前插入的数作为A[i]时在第j层产生的答案数,在统计完后,要把这个数插入到字典树中并更新num值和sum值
Code

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#include<queue>
#include<map>
#include<set>
#include<ctime>
using namespace std;
typedef long long ll;
typedef pair<int,int>P;
const int INF=0x3f3f3f3f,maxn=500001;
int ch[maxn*30][2],tot,num[maxn*30],cnt[maxn][30][2],T,n,a[maxn];
ll ans,sum[maxn*30];
//num[i]表示当前经过字典树的第i个节点的数的个数
//sum[i]表示当前字典树第i个节点作为A[k]时,在当前层满足条件的A[j]数(当前位与A[k]不同且编号小于A[k]的编号)
//cnt[i][j][k]表示1~i中在第j位为k的数的个数 
void Solve(int i)
{
    int p=1;
    for(int j=29;j>=0;j--)
    {
        int v=((a[i]&(1<<j))>0);
        int l=ch[p][v],r=ch[p][v^1];
        if(r)ans+=sum[r]-(ll)cnt[i][j][v]*num[r];//(j<k)-(j<=i)=i<j<k
        if(!l)break;
        p=l;
    }
    p=1;
    for(int j=29;j>=0;j--)
    {
        int v=((a[i]&(1<<j))>0);
        if(!ch[p][v])ch[p][v]=++tot;
        p=ch[p][v];
        num[p]++;//经过p节点的数字多了a[i] 
        //与经过p节点的A[k](即插入的a[i])在第j层不同的A[j]需要编号小于i且在当前位于A[k]不同
        sum[p]+=cnt[i-1][j][v^1]; 
    }
}
int main()
{
    scanf("%d",&T);
    while(T--)
    {
        for(int i=1;i<=tot;i++)
        {
            num[i]=sum[i]=0;
            for(int j=0;j<=1;j++)ch[i][j]=0; 
        }
        for(int i=1;i<=n;i++)
            for(int j=0;j<=29;j++)
                for(int k=0;k<=1;k++)   
                    cnt[i][j][k]=0;
        scanf("%d",&n);
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&a[i]);
            for(int j=0;j<=29;j++)
                for(int k=0;k<=1;k++)
                    cnt[i][j][k]=cnt[i-1][j][k];
            for(int j=0;j<=29;j++)
            {
                int v=((a[i]&(1<<j))>0);
                cnt[i][j][v]++;
            }
        }
        tot=1;ans=0;
        for(int i=n;i>=1;i--)Solve(i);
        printf("%I64d\n",ans);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值