HDU 6059 01字典树

题目链接


思路:
好题!

对于这类题,一个通用的切入口是枚举一个量,然后丢到01字典树里去匹配。

此题做法不止一种,可以枚举 a [ j ] a[j] a[j],然后 1 − ( j − 1 ) 1-(j-1) 1(j1)建立一棵前缀字典树, ( j + 1 ) − n (j+1)-n (j+1)n 建立一棵后缀字典树,然后分情况匹配即可。

但个人认为最简单和最容易理解的还是枚举 a [ k ] a[k] a[k],因为 i &lt; j &lt; k i&lt;j&lt;k i<j<k,故可以将 1 − ( k − 1 ) 1-(k-1) 1(k1) 的数丢进字典树,然后用 a [ k ] a[k] a[k]去匹配。

考虑 a [ i ] a[i] a[i] a [ k ] a[k] a[k]的关系。

因为他们异或的是一个相同的数 a [ j ] a[j] a[j]且异或后大小不等,则说明a[i],a[k]二进制分解后一定有不相同的位,且最高的不相同位之前的前缀一定对应相同。

所以当 a [ k ] a[k] a[k]在字典树匹配时,假设当前第 i i i位的数字为 c c c,则:
1 − ( i − 1 ) 1-(i-1) 1(i1)位与 a [ k ] a[k] a[k]相同,第 i i i位与 a [ k ] a[k] a[k]不同的数一定是合法的 a [ i ] a[i] a[i]

对于 a [ j ] a[j] a[j]的限制仅仅在于 a [ j ] a[j] a[j]的第 i i i位,设其数字为 x x x

c = 0 c = 0 c=0,则 x = 1 x = 1 x=1可保证 ( a [ i ] ⨁ a [ j ] ) &lt; ( a [ j ] ⨁ a [ k ] ) (a[i]\bigoplus a[j]) &lt; (a[j]\bigoplus a[k]) (a[i]a[j])<(a[j]a[k])
同理: c = 1 c = 1 c=1时, x = 0 x = 0 x=0符合题意。

故可以用一个数组 s u m [ i ] [ j ] sum[i][j] sum[i][j]维护第 i i i位为 j j j的数的个数。

最后注意去掉不合法的情况 i &gt; j i&gt;j i>j

可以维护一个变量 e x t ext ext,当插入 a [ k ] a[k] a[k]时,将其视为某个合法的 a [ i ] a[i] a[i],看前 1 − ( i − 1 ) 1-(i-1) 1(i1)个数有多少个数满足成为 a [ j ] a[j] a[j]的情况。


代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;

const int A = 5e5 + 10;
int sum[35][2],T[A<<5][2],tot,n;
ll ans,cnt[A<<5],ext[A<<5];

void init(){
    tot = ans = T[0][0] = T[0][1] = 0;
    memset(sum,0,sizeof(sum));
}

inline void calc(int u,ll all){ans += cnt[u]*(cnt[u]-1)/2 + (all-cnt[u])*cnt[u] - ext[u];}

void insert(ll v){
    int u = 0;
    for(int i=30 ;i>=0 ;i--){
        int c = (v>>i)&1;sum[i][c]++;
        if(!T[u][c]){
            T[u][c] = ++tot;
            T[tot][0] = T[tot][1] = 0;
            cnt[tot] = ext[tot] = 0;
        }
        if(T[u][c^1])  calc(T[u][c^1],sum[i][c^1]);
        u = T[u][c];
        cnt[u]++;
        ext[u] += sum[i][c] - cnt[u];
    }
}

int main(){
    int T;scanf("%d",&T);
    while(T--){
        init();
        scanf("%d",&n);
        for(int i=1 ;i<=n ;i++){
            int x;scanf("%d",&x);
            insert(x);
        }
        printf("%I64d\n",ans);
    }
    return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值