2017 Multi-University Training Contest - Team 3:1004. Kanade's trio(01字典树)



感觉很多时候字典树都是这种题的万能解

具体规则or什么是字典树:http://blog.csdn.net/jaihk662/article/details/53930927

建立两棵01字典树,对于当前第j个节点,将前面所有数加入第一棵字典树,将后面所有数加入第二棵字典树

之后查询就好了,如果两个数a[i]和a[k]前x位都相同,那么很显然这两个数异或a[j]前x位也都相同

所以你只要找出a[i]和a[k]第一个不同的地方进行异或比较就好


在字典树上的操作就是:对于两棵字典树所有相同的前缀,答案加上两棵树下一个不同的节点的权值积

例如:两棵树都经过了10010(root->R->L->L->R->L),那么

ans += sum1(100101)*sum2(100100)或者ans += sum1(100100)*sum2(100101)(具体加哪一个取决于a[j])


但相同前缀个数可能过多,所以要在树上求后缀

tre[]:字典树

val[now][t]:第t棵字典树now节点的权值(t=0那棵用来存前面的数)

frd[x]:节点x的兄弟(x和frd[x]父亲是同一个)

rek[i][]:存后缀,前i-1位都相同第i个数不同对答案的贡献


#include<stdio.h>
#include<string.h>
#define LL long long
int root, tre[3000055][2], val[3000055][2], rek[35][2], a[560000];
LL frd[3000055];
void Insert(LL x, int t)
{
	int i, c, now = 0;
	for(i=30;i>=0;i--)
	{
		c = 0;
		if(x&(1<<i))
			c = 1;
		if(tre[now][c]==0)
		{
			tre[now][c] = ++root;
			if(tre[now][c^1])
			{
				frd[root] = tre[now][c^1];
				frd[tre[now][c^1]] = root;
			}
		}
		now = tre[now][c];
		val[now][t] += 1;
		rek[i][c^t] += val[frd[now]][t^1];
	}
}
void Delete(LL x, int t)
{
	int c, i, now = 0;
	for(i=30;i>=0;i--)
	{
		c = 0;
		if(x&(1<<i))
			c = 1;
		now = tre[now][c];
		val[now][t] -= 1;
		rek[i][c^t] -= val[frd[now]][t^1];
	}
}
LL Query(LL x)
{
	int i, c;
	LL sum = 0;
	for(i=30;i>=0;i--)
	{
		c = 0;
		if(x&(1<<i))
			c = 1;
		sum += rek[i][c];
	}
	return sum;
}
int main(void)
{
	LL ans;
	int T, n, i;
	scanf("%d", &T);
	while(T--)
	{
		ans = root = 0;
		memset(tre, 0, sizeof(tre));
		memset(val, 0, sizeof(val));
		memset(rek, 0, sizeof(rek));
		memset(frd, 0, sizeof(frd));
		scanf("%d", &n);
		for(i=1;i<=n;i++)
			scanf("%d", &a[i]);
		for(i=2;i<=n;i++)
			Insert(a[i], 1);
		Insert(a[1], 0);
		for(i=2;i<=n;i++)
		{
			Delete(a[i], 1);
			ans += Query(a[i]);
			Insert(a[i], 0);
		}
		printf("%lld\n", ans);
	}
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值