Kanade's trio

Kanade's trio

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


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.

1T20

1n5105

0A[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
 

题意:

     给定一个数组A,取一个三元组(i,j,k),其中i<j<k,使得( A[i] xor A[j] ) < ( A[j] xor A[k] ),(xor为异或)。求这个三元组有多少种取法。


思路:

     用字典树处理。先在字典树中记录了A中的前k-1个数,现在以处理第k个数为例。

     首先由异或的特点(相异为1相同为0)我们可以得出,若使( A[i] xor A[j] ) < ( A[j] xor A[k] ),我们可以将A[k]与A[i]的二进制数位位对应,然后从高位开始,找到第一个不同的位置,记此位为t,(即记录二进制不同的最高位为t),此时只要保证A[i]与A[j]的第t位相同,就可以找到符合题意的情况。

     {说明:保证A[i]与A[j]的第t位相同是指,一种情况为A[i]与A[j]前t位均相同,则很容易就得出A[i] xor A[j]的结果是前t位为0,而A[j] xor A[k]的结果是前t-1位为0,t位为1,则符合题意;另一种是A[i]与A[j]前t-1位不同,第t位相同,则我们可以得出A[i] xor A[j]的结果前t-1位与A[j] xor A[k]结果相同,而第t位A[i] xor A[j]=0,而A[j] xor A[k]=1,所以也符合题意。}

     然后我们从t位着手,找到合适的i,j使符合题意。由上分析可得,结果可分两个部分计算。

     1. A[i],A[j],A[k]三者前t-1位都相同。这一部分我们通过分析发现,以与t同父结点的结点为根的子树中,所有的结点都可以选作i,j。故假设这一部分有n个结点,(n个结点中任取,最后小的是选作i,大的选作j,就符合题目中i<j的条件,其中因为到现在为止只记录了k个数,所以i,j都从前k-1个数中选取,自然符合i<k,j<k)。结果应为C(n,2),即n*(n-1)/2,可得程序中公式1。

     2. A[i]与A[k]的前t-1位相同,但A[i](A[k])与A[j]的前t-1位不同。这时我们可以从,以与t同父结点的结点为根的子树中选取i,然后为保证A[i]与A[j]的第t位相同,我们需要找到与t在同一层(同一深度)中,与A[i]的第t位相同的结点就可以,再从以这些结点为根的子树中找j。由此可得程序中公式2。但仔细分析我们不难发现,在公式2的计算方法中,i和j的取值和顺序是随意的,但是题目要求i和j的取值要符合i<j,所以我们就计算了非法部分,需要减去这一部分才是正确的结果。由此可得程序中公式3。


下面贴上代码:

#include<cstdio>
#include<cstring>
using namespace std;

typedef long long LL;
const int MX = 5e7 + 5;

struct node
{
    int nxt[2];
    LL sum[2];
    int v; //表示这个节点添加的次数
    void init()
    {
        sum[0] = sum[1] = 0; //在这个数的前面有多少个数的前t-1位与这个数的前t-1位是不同的(第t位相同)
        nxt[0] = nxt[1] = -1;
        v = 0;
    }
} L[MX];

int tot;
LL ans, cnt[32][2]; //cnt[i][0/1]表示有多少个数字的第i位是0/1

void add(int b[], int len)
{
    int now = 0, tmp;
    for (int i = len - 1; i >= 0; i--)
    {
        tmp = b[i]; //枚举A二进制每一位,记作tmp
        if (L[now].nxt[tmp] == -1) //进if,说明两个数(b[],b[]),i位置tmp的值不一样
        {
            L[++tot].init();
            L[now].nxt[tmp] = tot;
        }
        if (L[now].nxt[tmp ^ 1] != -1) //找到最高位不同的位置
        {
            ans += L[L[now].nxt[tmp ^ 1]].v * (L[L[now].nxt[tmp ^ 1]].v - 1) / 2;
            //Ai,Aj和Ak的前t-1位都相同时对答案的贡献
            ans += L[L[now].nxt[tmp ^ 1]].v * (cnt[i][tmp ^ 1] - L[L[now].nxt[tmp ^ 1]].v);
            //Ai的前t-1位与Ak的前t-1相同,Aj的前t-1位不与Ak的前t-1相同时对答案的贡献
            ans -= L[now].sum[tmp ^ 1];
            //Ai的前t-1位与Ak的前t-1位不同时对答案的贡献(负,第二种情况中误加)
        }
        L[now].sum[tmp] += cnt[i][tmp] - L[L[now].nxt[tmp]].v;
        cnt[i][tmp] ++;
        now = L[now].nxt[tmp];
        L[now].v++;
    }
}
int a[500005], b[35];

int main()
{
    int T, n;
    scanf("%d", &T);
    while (T--)
    {
        scanf("%d", &n);
        for (int i = 1; i <= n; i++)
            scanf("%d", &a[i]);

        memset(cnt, 0, sizeof(cnt));

        tot = ans = 0;

        L[0].init();

        for (int i = 1; i <= n; i++)
        {
            for (int j = 0; j < 30; j++)
                b[j] = a[i] >> j & 1;
            add(b, 30);
        }

        printf("%lld\n", ans);
    }
    return 0;
}




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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值