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;
}