HDU 6059 Kanade's trio【字典树】

题目来戳呀

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 ( Ai xor Aj )< ( Aj xor Ak )
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

Source

2017 Multi-University Training Contest - Team 3

题意:

给你数组长度为n的数组A,求出三元组(i,j,k)的个数,满足( Ai xor Aj )< ( Aj xor Ak )

想法:

我们来枚举k的值,运用字典树。
我们假设 Ai Ak 的最高不同位为第t位,因为 Ai Ak 第t位之前的一定是全部相同的,所以只要 Aj 的第t位与 Ai 相等异或此位结果为0, Aj 的第t位与 Ak 相反此位异或结果为1,就能满足异或不等式。
从而 Aj 第t位之前是随意取的,因为 Ai Ak 第t位之前的一定是全部相同的,二者与 Aj 第t位之前异或的结果是相等的; Aj 第t位之后也是随意取的,因为上一段已经确定了 Aj 第t位的值了,之后的位异或后再大再小都不会影响高位。
下面画个图举个栗子:
计算1 2 6 8 10 中的三元组个数,先画个字典树 好说明一下:
这里写图片描述
我们从第t位着手,分为两种情况。
1. A[i],A[j],A[k]三者前t-1位都相同。这一部分我们通过分析发现,以与t同父结点的结点为根的子树中,所有的结点都可以选作A[i],A[j],并且默认顺序小的做i,大的做j。故假设这一部分有n个结点,结果应为C(n,2),即n*(n-1)/2。(比如图中A[k]是当前的0110,那A[i]和A[j]可以从0001,0010中任选2个分别作为A[i]和A[j],当然此处只有2个能选+_+)
2. A[i]与A[k]的前t-1位相同,但A[i]与A[j]的前t-1位不同。这时我们可以从,以与t同父结点的结点为根的子树中选取i,然后为保证A[i]与A[j]的第t位相同,我们需要找到与t在同一层(同一深度)中,与A[i]的第t位相同的结点就可以,再从以这些结点为根的子树中找j。(比如A[k]是当前的0110,A[i]选择左子树的0010,A[j]选择右子树的1000)
但仔细分析我们不难发现,i和j的取值和顺序此时是随意的,但是题目要求i和j的取值要符合i

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=5e5+10;
int a[maxn],num[30];
int cnt[2][31];///cnt[i][j]表示第j位上是i的有多少个
ll ans;
int tsize;
struct node
{
    int v;///表示这个节点被经过的次数(数中某位为此节点的个数)
    int nxt[2];///此节点表示的值
    int ext;
} T[maxn*31];
void solve()
{
    int cou=0;///表示树的层数 即第几位 枚举二进制数的每一位
    for(int i=0;i<30;++i)
    {
        if(!T[cou].nxt[num[i]])///给存在数的位置上编号?
        {
            ++tsize;///每个节点的编号
            T[cou].nxt[num[i]]=tsize;
        }
        if(T[cou].nxt[1^num[i]])///找到最高位不同的位置(t)
        {
            ans+=T[T[cou].nxt[1^num[i]]].v*1ll*(T[T[cou].nxt[1^num[i]]].v-1)/2;///和从k不同的此位上的数里C(v,2) v表示有几个数此位和k不同(小的做i 大的做j)
            ans+=T[T[cou].nxt[1^num[i]]].v*1ll*(cnt[1^num[i]][i]-T[T[cou].nxt[1^num[i]]].v);///左边拿一个 右边拿一个(但是i j的顺序是随意的)
            ans-=T[T[cou].nxt[1-num[i]]].ext;///在此节点的前面有多少个节点的前t-1位与这个数的前t-1位是不同的(第t位相同) (i成为了不同的这些大数的情况)
        }
        cou=T[cou].nxt[num[i]];
        T[cou].v++;
        T[cou].ext+=cnt[num[i]][i]-T[cou].v;
    }
}
int main()
{
    int t,n;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d",&n);
        memset(T,0,sizeof T);
        memset(num,0,sizeof num);
        memset(cnt,0,sizeof cnt);
        ans=tsize=0;
        for(int i=0; i<n; ++i)
        {
            scanf("%d",&a[i]);
            for(int j=29; j>=0; --j)
            {
                num[j]=a[i]%2;
                cnt[a[i]%2][j]++;
                a[i]/=2;
            }
            solve();
        }
        printf("%lld\n",ans);

    }
}

ps:代码好拿写QAQ特别是ext排除i>j那里,debug一整天恍恍惚惚。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值