2018.09.30【LOJ517】「LibreOJ β Round #2」计算几何瞎暴力(01Trie)(二进制拆分)

本文深入探讨了如何运用01Trie数据结构和二进制拆分技巧解决涉及全局异或操作的复杂问题。通过具体实例,讲解了在不同操作下,如插入、查询区间和、全局异或及排序时,如何高效维护数据结构的状态。文章详细解释了如何在01Trie中统计子树内各二进制位的出现次数,并利用这些信息进行快速查询和更新。

传送门


解析:

看到标题的 d a l a o dalao dalao先不要急着锤我。。。
这道题的二进制拆分和 01 T r i e 01Trie 01Trie不能混在一起,不要急着说 01 T r i e 01Trie 01Trie就是二进制拆分。。。

思路:

这道题可以说是非常好的一道数据结构。
我相信应该没有人会去想计算几何。(那这出题人得有多善良)

先看操作1,要求在末尾插入一个数,这个与数据结构没有什么关系,我们可以直接在数据结构外建一个数组存。

操作2,询问区间和,这个就是维护前缀和,还是不能帮助我们确定使用什么数据结构。

操作3,全局 x o r xor xor
空气凝固。。。这个除了 01 T r i e 01Trie 01Trie还能怎么做?

操作4,全局排序。。。 01 T r i e 01Trie 01Trie内部的数字本来就是排了序的。我们只需要记录之前 x o r xor xor了一个什么东西就能够确定内部具体的顺序,在每次排序的时候将相数组里面的数全部插入 T r i e Trie Trie树里面。

那这道题就是 01 T r i e 01Trie 01Trie咕咕咕了。

好,最(被和谐)的地方到了。

对于一个有全局 x o r xor xor的题,我们要怎么维护前缀和?

这里就是刚才提到的二进制拆分的作用了。

对于所有的 x o r xor xor我们都不直接作用在数组和 T r i e Trie Trie树上,而是用一个标记记录当前应该要 x o r xor xor上一个什么东西。

而前缀和,我们将所有数的二进制表示拆开,分位统计前缀和(统计该位出现次数)。记录在 s u m sum sum数组里面。那么 s u m [ x ] [ i ] sum[x][i] sum[x][i]表示前 x x x位中在第 i i i位出现了多少个 1 1 1,那么出现的 0 0 0的个数就是 x − s u m [ x ] [ i ] x-sum[x][i] xsum[x][i]。这两个个数都是在异或上 x o r t a g xortag xortag之前而言的

每次询问一个位置,我们就看一下 x o r t a g xortag xortag这一位是否是1,而采取是统计 s u m [ x ] [ i ] sum[x][i] sum[x][i]还是 x − s u m [ x ] [ i ] x-sum[x][i] xsum[x][i]

同理,在 T r i e Trie Trie树中,我们统计每个节点子树出现各个位的次数。在记录前缀和的时候根据子树大小调整就行了。
不要忘了在叶子节点的时候,只能统计该统计的部分,这个需要单独处理一下。

假装被和谐掉了的复杂度分析

根据我们刚才的叙述,我们可以得到一个不太对的复杂度。

单次 x o r xor xor的复杂度 O ( 1 ) O(1) O(1)
T r i e Trie Trie树单次插入的复杂度 O ( l o g 2 A i ) O(log^2A_i) O(log2Ai)(插入+二进制前缀和)
T r i e Trie Trie树单次询问的复杂度 O ( l o g 2 A i ) O(log^2A_i) O(log2Ai)
数组单次插入 O ( l o g A i ) O(logA_i) O(logAi)
数组单次询问复杂度 O ( l o g A i ) O(logA_i) O(logAi)

单次排序复杂度 O ( 数 组 中 数 个 数 × T r i e 的 插 入 复 杂 度 ) O(数组中数个数\times Trie的插入复杂度) O(×Trie)

而总复杂度可以近似认为是 O ( ( n + m ) l o g n ∗ l o g A ) O((n+m)logn*logA) O((n+m)lognlogA)


代码:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define re register
#define gc getchar
#define pc putchar
#define cs const

inline
ll getint(){
	re ll num;
	re char c;
	while(!isdigit(c=gc()));num=c^48;
	while(isdigit(c=gc()))num=(num<<1)+(num<<3)+(c^48);
	return num;
}

inline
void outint(ll a){
	static char ch[23];
	if(0==a)pc('0');
	while(a)ch[++ch[0]]=a-a/10*10,a/=10;
	while(ch[0])pc(ch[ch[0]--]^48);
}

cs int N=100005;

int xortag;
struct _01TRIE{
	#define root 0
	int son[N*30][2];
	int siz[N*30];
	int sum[N*30][30];
	int tot;
	int tag;
	_01TRIE(){tot=0;}
	
	void insert(int a){
		int now=root;
		for(int re i=29;~i;--i){
			bool f=a&(1<<i);
			if(!son[now][f])son[now][f]=++tot;
			now=son[now][f];
			++siz[now];
			for(int re j=29;~j;--j){
				if(a&(1<<j))++sum[now][j];
			}
		}
	}
	
	ll querysubtree(int pos){
		ll ans=0;
		for(int re i=29;~i;--i)
		if(xortag&(1<<i))ans+=(1ll*siz[pos]-sum[pos][i])<<i;
		else ans+=sum[pos][i]*1ll<<i;
		return ans;
	}
	
	ll querysum(int x){
		if(x==0)return 0;
		ll ans=0;
		int now=root;
		for(int re i=29;~i;--i){
			bool f=0;
			if(tag&(1<<i))f^=1;
			if(x<=siz[son[now][f]])now=son[now][f];
			else{
				ans+=querysubtree(son[now][f]);
				x-=siz[son[now][f]];
				now=son[now][!f];
			}
		}
		ans+=querysubtree(now)/siz[now]*x;
		return ans;
	}
	
	int size(){
		return siz[son[root][0]]+siz[son[root][1]];
	}
	
	#undef root
}Trie;

struct array{
	int tot;
	int sum[N][30];
	int h[N];
	
	void insert(int a){
		h[++tot]=(a^=xortag);
		for(int re i=29;~i;--i)
		sum[tot][i]=sum[tot-1][i]+((a>>i)&1);
	}
	
	ll querysum(int pos){
		ll ans=0;
		for(int re i=29;~i;--i)
		if(xortag&(1<<i))ans+=(1ll*pos-sum[pos][i])<<i;
		else ans+=sum[pos][i]*1ll<<i;
		return ans;
	}
	
	void sort(){
		while(tot)Trie.insert(h[tot--]);
		Trie.tag=xortag;
	}
	
}Array;

inline
ll query(int pos){
	if(pos>Trie.size())return Trie.querysum(Trie.size())+Array.querysum(pos-Trie.size());
	return Trie.querysum(pos);
}

int n,m;
signed main(){
	n=getint();
	for(int re i=1;i<=n;++i){
		int a=getint();
		Array.insert(a);
	}
	m=getint();
	while(m--){
		int op=getint();
		switch(op){
			case 1:{
				int x=getint();
				Array.insert(x);
				break;
			}
			case 2:{
				int l=getint(),r=getint();
				outint(query(r)-query(l-1));pc('\n');
				break;
			}
			case 3:{
				xortag^=getint();
				break;
			}
			case 4:{
				Array.sort();
				break;
			}
		}
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值