HDU 6059 Kanade's trio(思维+字典树,好题)

Kanade's trio

Time Limit: 4000/2000 MS (Java/Others)    Memory Limit: 524288/524288 K (Java/Others)
Total Submission(s): 1478    Accepted Submission(s): 548


 

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.

1≤T≤20

1≤∑n≤5∗105

0≤A[i]<230

 

 

Input

There is only one integer T on first line.

For each test case , the first line consists of one integer n ,and the second line consists of n integers which means the array A[1..n]

 

 

Output

For each test case , output an integer , which means the answer.

 

 

Sample Input

 

1 5 1 2 3 4 5

 

 

Sample Output

 

6

题意:给你n个数,问你多少个三元组(i,j,k)满足a[i]^a[j]<a[j]^a[k]且i<j<k,其中^为异或。

思路:这个题与 CCPC-Wannafly Winter Camp Day7 (Div2, onsite) F 逆序对! (01字典树的O(nlogn)做法) 原理相似。

但是本题求的是三元组的个数,所以主要还是在于如何处理i<j<k。

考虑到使a[i]^a[j]<a[j]^a[k]得条件是 设x是a[i]与a[k]的最高不相同位,则a[j]的第x位与a[i]相同,与a[k]相反。

我们考虑从n到1倒着枚举a[i]。对于第x位,用sum[u]表示经过字典树上u节点的数作为a[k]时,下标比k小且在当前层第x位与a[k]相反的数字个数(即满足条件的a[j]的数量)。设num[u]为经过字典树上u节点的数字的个数,每次插入a[i]时,考虑它在第x层(第x位)产生的答案。

假设它在第y位为id(id=0或1),对应字典树上的节点l=ch[u][id],对偶节点r=ch[u][id^1],那么在当前位下标大于i的k有num[r]个,但是sum[u]中包含了下标小于等于i的a[j],于是设cnt[i][x][id]为前i个数中第x位为id的数字的个数,这些数中的每一个数(即a[j])都可以和a[i],num[r]中的每一个数(即a[k])组成一个只有下标不合法(j<=i<k)的满足异或要求的三元组(i,j,k)。因此直接从sum[r]中减去即可。

即若节点r存在,则对答案的贡献为ans+=sum[r]-num[r]*cnt[i][x][id];

在统计完第i个数的贡献值后,即可把他插入到字典树中,更新对应的num数组(每一个经过的节点值++)和sum数组(加上cnt[i-1][x][id^1],即加上这个数作为a[k]时候下标小于该数的a[j]的数量)。

需要注意初始化以及数组大小。

代码:

#include <bits/stdc++.h>
#define ll long long
#define inf 0x3f3f3f3f
using namespace std;
const int maxn=5e5+5;
int n,m,k,q;
ll ans;
ll a[maxn];
ll sum[maxn*31];
int cnt[maxn][32][2],szz;
struct Trie
{
    int ch[maxn*31][2];
    int val[maxn*31];
    int sz;
    void init()
    {
        for(int i=0;i<=szz;i++) sum[i]=val[i]=0;
        memset(ch[0],0,sizeof(ch[0]));
        sz=0;
    }
    void add(int x,int idx)
    {
        int u=0;
        for(int i=30;i>=0;i--)
        {
            int id=((x>>i)&1);
            int l=ch[u][id],r=ch[u][id^1];
            if(r) ans+=sum[r]-val[r]*(ll)cnt[idx][i][id];//
            u=ch[u][id];
            if(!u) break;//
        }
        u=0;
        for(int i=30;i>=0;i--)
        {
            int id=(x>>i)&1;
            if(!ch[u][id])
            {
                ch[u][id]=++sz;
                memset(ch[sz],0,sizeof(ch[sz]));
                val[sz]=0;
            }
            u=ch[u][id];//
            sum[u]+=cnt[idx-1][i][id^1];//
            val[u]++;//
        }
    }
}t;
int main()
{
    int T,cas=1;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d",&n);
        t.init();
        ans=0;
        for(int i=1;i<=n;i++)
        {
            scanf("%lld",&a[i]);
            for(int j=0;j<31;j++)
            for(int k=0;k<2;k++)
            cnt[i][j][k]=0;
        }
        for(int i=1;i<=n;i++)
        {
            for(int j=0;j<31;j++)
            {
                cnt[i][j][0]=cnt[i-1][j][0];
                cnt[i][j][1]=cnt[i-1][j][1];
                cnt[i][j][((a[i]>>j)&1)]++;
            }
        }
        for(int i=n;i>=1;i--) t.add(a[i],i);
        printf("%lld\n",ans);
        szz=t.sz;
    }
    return 0;
}


 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值