HDU6059 01字典树

题解: 
  参考大佬的思路和代码,悟出一些自己不懂的东西。
 很多异或题都有可能用到01字典树,因为它的时间复杂度为O(nlogAi).
 我们先了解一下数组,比如用cnt[30][2]来表示已插入字典树中的数字中每一位的0/1有多少个,num[]数组表示的是第k个数的每一位。
 我们先将a[]数组里的数逐个变成二进制倒放进去字典树中并遍历a[k],从高位到低位查找字典树里面第一个与a[k]的第x位不同的值(即是大佬所说的最高的不同的位)。
 然后我们设a[i]的前面的高位与a[k]前面的高位相同,第x位不同,请记住这一点!!!不然后面容易搞不清楚情况。
 然后我们就要去找a[j]了,如果a[k]的第x位是1的话,那么a[i]和a[j]的第x位就要是0,或者如果a[k]的第x位是0的话,那么a[i]和a[j]的第x位就都要是1,不然就无法出现a[k]^a[i]>a[i]^a[j],k>j>i;
 忘记说了一点就是a[j]前面的高位要与a[k]前面的高位相同吗?这就要分两种情况讨论了,(是在我们确定了a[j]的第x位要与a[i]的第x位要相同的思路下)
 第一种是a[j]的前面的高位与a[i]的前面的高位相同,即a[i]的字典树里面的信息可以当成是a[j]的字典树的信息来使用那么我们就可以直接用C(cnt,2)来计算了,即是t[u].cnt*(t[u].cnt-1)/2
 第二种就是a[j]的前面的高位与a[i]的前面的高位不相同,即是a[j]在字典树的别的分支上,与a[i]不同支,但是a[j]的第x位满足与a[i]的第x位相等,就相当于它只要在第x位所在的这一层中与a[i]的第x位相同的子树中就可以了,记得是这一层中满足a[i]与a[j]第x位相同的这一层中,不要搞错了。
 然后我们就可以计算了,t[u].cnt*(c-t[u].cnt)。
 这就完事了,真的完事了吗?并没有,因为题目要求的是k>j>i,那么我们就会算多了i>j的情况,这是不允许的,那么我们就要减去i>j的情况,
 怎么操作的呢?ORZ其实我也不会,我是看大佬的操作的,他用一个ext值,说的是:如果把当前的插入的节点当做 Ai(而不是 Ak)的话,那在它之前就已经插入的x位也为这个值的数,虽然x位值相同,但违反 i < j 的规定,把这些数的个数记下,在算贡献时减去。
 所以还要减去t[u].ext。
 这道题就可以过了。。。ORZ但是事后我还是做不出来,果然还是太LJ了。
参考博客:

https://blog.csdn.net/HackerTom/article/details/76652749
https://blog.csdn.net/Cai_Haiq/article/details/76704581
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
#define LL long long int
const int MAXN=5e5+7;
struct node
{
	int next[2];
	int cnt,ext;//cnt记录这个点被经过了几次。
	void init()
	{
		next[0]=next[1]=0;
		cnt=0;
		ext=0;
	} 
}t[MAXN*31];
LL ans,ans2;
int cnt[31][2],a[MAXN],num[31];
int tot; 
void cla(int u,int c)
{
	ans+=(LL)t[u].cnt*(LL)(t[u].cnt-1)/2;//如果i,j的高位与k的高位相同,而第x位都与k的第x位不同,则任意从中选两个,C(v,2)中选两个
	ans2+=(LL)t[u].cnt*(LL)(c-t[u].cnt)-t[u].ext;//这里求的是第二种情况,i的高位与j的高位不相同,但是第x位都相同而与k的第x位不同,最终还要减去之间标记过j>i的情况
}
void insert()
{
	int u=0;
	for(int i=1;i<=30;i++)
	{
		if(!t[u].next[num[i]]){
		 	t[++tot].init();
			t[u].next[num[i]]=tot;	
		} 
		if(t[u].next[num[i]^1])如果发现第x位不同的数则进行计算 
			cla(t[u].next[num[i]^1],cnt[i][num[i]^1]);
		u=t[u].next[num[i]];
		t[u].cnt++;
		t[u].ext+=cnt[i][num[i]]-t[u].cnt;//把此处的k当做i处理,寻找不符合要求的j,并记录
	}
}
int main()
{
	int T,n;
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d",&n);
		memset(num,0,sizeof(num));
		memset(cnt,0,sizeof(cnt));
//		memset(t,0,sizeof(t)); 用这种方式清0时间很长。 
		ans=0;
		ans2=0;
		tot=0;
		t[0].init(); 
		for(int i=1;i<=n;i++)
		{
			scanf("%d",&a[i]);
			int temp=a[i];
			for(int j=30;j>=1;j--)
			{
				num[j]=temp%2;
				cnt[j][temp%2]++;
				temp/=2;
			}
			insert();
		}
		printf("%lld\n",ans+ans2);
	}
} 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值