0-1 字典树(异或小能手)

0-1字典树

相信大家都会字典树了,字典树可以用来节省空间复杂度, 即 最简单的多个单词 公用一个字母节点。
如果在字典树上建fail边,就变成AC自动机啦(这个有机会下次说)。

今天的主角 0-1 字典树,还能节约时间复杂度!
0-1字典树,顾名思义每个节点不是0就是1,0-1字典树最多有32层,每层代表一个二进制位。
他能实现 诸如 异或不等式的问题,在每个数字在边插入时边维护答案。

这里有三道例题,以后自己看得时候,不要嫌弃自己现在的代码 吃藕 555~

Hdu 6955 Xor sum

题意:
给你 一个长度为1e5的序列,问 最短的区间 使得区间中数的异或大于等于m;
输出区间左右端点;
思路:
异或具有前缀和的性质,例如我们求 l~r 区间内数的异或值 等于 sum[r] ^ sum[l-1];
我们先处理出异或前缀和,然后在插入时查看是否有异或值大于等于m的情况,并维护答案。
如:m在pos位是0,如果我们能异或出1那么就能直接成功,否则我们就一直朝着m的二进制去靠拢。

int t;
ll n,m;
int ss[35];
struct node{
	int vis[2],val;
}trie[5000060];
ll s[100060];
int l,r;
ll tot=0;
void insert(ll p,int u){
	int now=0;
	int x[35];
	for(int i=31;i>=1;i--){
		x[i]=p%2;p/=2;
	}
	for(int i=2;i<=31;i++){
		if(trie[now].vis[x[i]])now=trie[now].vis[x[i]];
		else {
			trie[now].vis[x[i]]=++tot;
			now=trie[now].vis[x[i]];
		}
		trie[now].val=u;
	}
}
void find(ll p,int u){
	int now=0;
	int x[35];
	for(int i=31;i>=1;i--){
		x[i]=p%2;p/=2;
	}
	int f=1;
	for(int i=1;i<31;i++){
		if(ss[i+1]==0&&x[i+1]==0&&trie[now].vis[1]){
			if(!l&&!r)l=trie[trie[now].vis[1]].val,r=u;
			else if(u-trie[trie[now].vis[1]].val<r-l){
				l=trie[trie[now].vis[1]].val,r=u;
			}else if(u-trie[trie[now].vis[1]].val==r-l&&u<r){
				l=trie[trie[now].vis[1]].val,r=u;
			}
		}
		if(ss[i+1]==0&&x[i+1]==1&&trie[now].vis[0]){
			if(!l&&!r)l=trie[trie[now].vis[0]].val,r=u;
			else if(u-trie[trie[now].vis[0]].val<r-l){
				l=trie[trie[now].vis[0]].val,r=u;
			}else if(u-trie[trie[now].vis[0]].val==r-l&&u<r){
				l=trie[trie[now].vis[0]].val,r=u;
			}
		}
		if(ss[i+1]==0&&x[i+1]==1&&trie[now].vis[1])now=trie[now].vis[1];
		else if(ss[i+1]==0&&x[i+1]==0&&trie[now].vis[0])now=trie[now].vis[0];
		else if(ss[i+1]==1&&x[i+1]==0&&trie[now].vis[1])now=trie[now].vis[1];
		else if(ss[i+1]==1&&x[i+1]==1&&trie[now].vis[0]){
			now=trie[now].vis[0];
		}
		else {
			f=0;break;
		}
	}
	if(f){
		if(!l&&!r)l=trie[now].val,r=u;
		else if(u-trie[now].val<r-l){
			l=trie[now].val,r=u;
		}else if(u-trie[now].val==r-l&&u<r){
			l=trie[now].val,r=u;
		}
	}
}
int main(){
	scanf("%d",&t);
	while(t--){
		l=0,r=0;tot=0;
		scanf("%lld%lld",&n,&m);
		s[0]=0;
		for(int i=1;i<=n;i++){
			scanf("%lld",&s[i]);
			s[i]=s[i]^s[i-1];
		}
		for(int i=31;i>=1;i--){
			ss[i]=m%2;m/=2;
		}
		for(int i=1;i<=n;i++){
			insert(s[i],i);
			find(s[i],i);
		}
		if(l==r&&!l)printf("-1\n");
		else printf("%d %d\n",l+1,r);
		for(int i=0;i<=tot;i++){
			trie[i].vis[0]=trie[i].vis[1]=0;
		}
	}
	return 0;
}

Hdu 6964 I love counting

题意:
给你长度1e5的序列,1e5组询问,询问区间l到r有多少个不一样的数满足 异或a 小于等于b;

思路:
题目求有多少个数 异或a小于等于b,我们考虑字典树上走 dfs,

int n,m,s[maxn],ans[maxn];
int trie[400*maxn][2],sum[400*maxn];
int cnt=0;
int root[maxn];
int vis[maxn];
struct node{
	int l,a,b,i;
};
vector<node>v[maxn];
int lowbit(int x){
	return x&(-x);
}
void insert(int &id,int v,int x){
	if(!id)id=++cnt;
	int u=id;
	for(int i=20;i>=0;i--){
		int p=x>>i&1;
		if(trie[u][p]==0)trie[u][p]=++cnt;
		u=trie[u][p];
		sum[u]+=v;
	}
}
int query(int id,int a,int b){
	if(id==0)return 0;
	int u=id;
	int ans=0;
	for(int i=20;i>=0;i--){
		int p1=a>>i&1;
		int p2=b>>i&1;
		if(p2){
			if(p1)ans+=sum[trie[u][1]],u=trie[u][0];
			else ans+=sum[trie[u][0]],u=trie[u][1];
		}else{
			if(p1)u=trie[u][1];
			else u=trie[u][0];
		}
		if(!u)break;
	} 
	return ans+sum[u];
}
void add(int u,int v,int x){
	while(u<=n){
		insert(root[u],v,x);
		u+=lowbit(u);
	}
}
int cal(int x,int a,int b){
	int ans=0;
	while(x){
		ans+=query(root[x],a,b);
		x-=lowbit(x);
	}
	return ans;
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++)scanf("%d",&s[i]);
	scanf("%d",&m);
	for(int i=1;i<=m;i++){
		int l,r,a,b;scanf("%d%d%d%d",&l,&r,&a,&b);
		v[r].push_back({l,a,b,i});
	}
	for(int i=1;i<=n;i++){
		if(vis[s[i]])add(vis[s[i]],-1,s[i]);
		vis[s[i]]=i;
		add(i,1,s[i]);
		for(auto it: v[i]){
			int a=it.a,b=it.b;
			ans[it.i]=cal(i,a,b)-cal(it.l-1,a,b);
		}
	}
	for(int i=1;i<=m;i++){
		printf("%d\n",ans[i]);
	}
	return 0;
} 

Tree Xor

题意:

//给你n(n<=1e5)个节点的树,每个节点的权值有一个范围,即 l[i]<=x<=r[i];
//给你每条边连接的两点的异或值,求有多少种方案使得这棵树成立。

思路:

//我们不妨先定一个点为根(我取的是 root=1 ),然后把root节点的权值暂定为0,
//我们在dfs的过程中,先预处理出满足连边的异或值 的其他所有点的权值x[i]。
//然后问题就转化成了有多少的数a,满足使得 
//l[i]<=x[i]^a<=r[i];(边两边的点异或同一个值,两点的异或值不变)
//易得a就属于 x[i]^w; l[i]<=w<=r[i];
//w是连续的一段区间,但是w异或上x[i]就不是一段连续的区间了。
//但是我们知道在形如xxxx000~xxxx111这样的区间中,这个区间的数和一个数异或还是一个连续的区间。
//那么我们就可以根据这个性质,将区间,l[i]~r[i]分成log个区间,然后分别去和w[i]做异或,
//就可以得到0/1字典树上的一个节点了。
//这个有一个绝妙的想法,我们利用一颗0~(1<<30)-1的权值线段树,那么每个区间的范围都是形如:
//xxx000~xxx111的,那么我们在线段树上走dfs的同时,利用区间的对应位上的值和w[i]上位的值,
//就能把 异或区间分段,并且处理出节点信息,从而维护出了这样的字典树;
//
//如果从正面考虑,就要求nlogn个区间的交集,我们要处理处所有的区间,然后差分求答案。
//我们从反向考虑,不能满足不等式的数我们标记1,然后最后的ans=区间总数-标1的个数

int n;
int l[100040],r[100040];
struct node{
	int to;ll w;
};
vector<node>v[100040];
int w[100040];
void dfs(int x,int pre){
	for(int i=0;i<v[x].size();i++){
		node p=v[x][i];
		if(p.to!=pre){
			w[p.to]=w[x]^p.w;
			dfs(p.to,x);
		}
	}
}
int sum[6000050],ls[6000030],rs[6000040];
int cnt=0;
int rt=0;
int k;
void update(int &rt,int l,int r,int x,int y,int bit){
	if(x>y)return;
	if(rt==0)rt=++cnt;
	if(sum[rt]==r-l+1)return;
	if(x<=l&&r<=y){
		sum[rt]=r-l+1;
		return;
	}
	int mid=l+r>>1;
	if(x<=mid){
		if((k>>bit)&1){
			update(rs[rt],l,mid,x,y,bit-1);
		}else update(ls[rt],l,mid,x,y,bit-1);
	}
	if(mid<y){
		if((k>>bit)&1){
			update(ls[rt],mid+1,r,x,y,bit-1);
		}else update(rs[rt],mid+1,r,x,y,bit-1);
	}
	sum[rt]=sum[ls[rt]]+sum[rs[rt]];
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		scanf("%d%d",&l[i],&r[i]);
	}
	for(int i=1;i<n;i++){
		int a,b,c;
		scanf("%d%d%d",&a,&b,&c);
		v[a].push_back({b,c});
		v[b].push_back({a,c});
	}
	w[1]=0;
	dfs(1,0);
	for(int i=1;i<=n;i++){
		k=w[i];
		update(rt,0,(1<<30)-1,0,l[i]-1,29);
		update(rt,0,(1<<30)-1,r[i]+1,1500000000,29);
	}
	printf("%d\n",(1<<30)-sum[rt]);
    return 0;
}


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值