题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6059
题意:如题。
解法:这道题,赛后一时没有懂,今天拿出来看了题解,瞬间懂了,开深。我把自己对这道题的理解写一下。
1,需要做什么?我们需要枚举数字的每一位,建立一个前缀字典树,sum【】记录这个节点添加的个数,x【】代表不符合要减去的个数,tot【i】【j】代表第i位的第j位的 数量。
2,怎么做?枚举a[k]的每一位,对于a[k]的第cur位Kcurp,如果Kcurp等于1的话,那么对答案产生贡献的Icurp和Jcurp(Icurp代表a[i]的第cur位,Jcurp代表a[j]的第cur位)一定为0,而且a[i]的前cur位和a[k]的前cur位必定相同,这个自己推一推就明白了,然后再记一下a[Kcurp]对应的节点为p,同父亲的另一个儿子对应pp。
那么如何计算答案?
3,如果从0-Kcurp的 每一位,a[i],a[j],a[k]都相等时,ak[Kcurp]加入后贡献是sum[p]*(sum[p]-1)/2.
4,ai【0】-ai【Kcurp-1】和aj【0】-aj【Kcurp-1】有部分不想等,ai【0】~ai【Kcurp-1】和ak【0】~ak【Kcurp-1】相等时,贡献是sum[p]*(tot[i][b[i]^1]-sum[p])-x[p]。(下面说x[p])
5,x【p】表示加入一个点,产生的不合法贡献,就是tot[i][b[i]]-sum[pp].
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int maxn = 500010;
const int maxs = 35;
int T,n,b[maxs];
int cnt,sum[maxn*maxs],ch[maxn*maxs][2],tot[maxs][2];
LL ans,x[maxn*maxs];
void insert()
{
int now = 0;
for(int i = 0; i < 31; i++){
int go = b[i];
if(!ch[now][go]){
ch[now][go] = ++cnt;
memset(ch[cnt], 0, sizeof(ch[cnt]));
x[cnt] = sum[cnt] = 0;
}
//cal answer
int p = ch[now][go^1], pp = ch[now][go];
ans += 1LL*sum[p]*(sum[p]-1)/2;
ans += 1LL*sum[p]*(tot[i][go^1]-sum[p])-x[p];
x[pp] += tot[i][go] - sum[pp];
sum[pp]++;
tot[i][go]++;
now = ch[now][go];
}
}
int main()
{
scanf("%d", &T);
while(T--)
{
scanf("%d", &n);
cnt = 0;
ans = 0;
sum[0] = x[0] = 0;
memset(tot, 0, sizeof(tot));
memset(ch[0], 0, sizeof(ch[0]));
for(int i=1; i<=n; i++){
int v;
scanf("%d", &v);
for(int j=30; j>=0; j--){
b[j] = v&1;
v >>= 1;
}
insert();
}
printf("%lld\n", ans);
}
return 0;
}