【学习笔记】trie树

[BJOI2016]IP地址

数据结构题。

没啥好说的。难点在于想到打懒惰标记。

简单说一下吧。设 f i f_i fi表示节点 i i i的变化次数。下传标记的充要条件是子节点不含插入字符串。

#include<bits/stdc++.h> #define fi first #define se second using namespace std; int n,m,cnt; int trie[100005*64][2],tot,res[100005],op[100005]; int f[100005*64],tag[100005*64],v[100005*64]; string s[100005]; struct node{ string s; int x,id,op; bool operator <(const node &a)const { return x<a.x; } }q[200005]; void ins(string s){ int it=0; for(int i=0;i<s.size();i++){ if(!trie[it][s[i]-'0'])trie[it][s[i]-'0']=++tot; it=trie[it][s[i]-'0']; } } void pushdown(int it){ if(!it)return; int l=trie[it][0],r=trie[it][1]; if(tag[it]){ if(!v[l])tag[l]+=tag[it],f[l]+=tag[it]; if(!v[r])tag[r]+=tag[it],f[r]+=tag[it]; tag[it]=0; } } void upd(string s){ int it=0; for(int i=0;i<s.size();i++){ pushdown(it); if(!(it=trie[it][s[i]-'0']))return; }f[it]++,tag[it]++,v[it]^=1; } int qry(string s){ int it=0; for(int i=0;i<s.size();i++){ pushdown(it),it=trie[it][s[i]-'0']; }return f[it]; } int main(){ ios::sync_with_stdio(false); cin.tie(0),cout.tie(0); cin>>n>>m; for(int i=1;i<=n;i++){ string tmp; cin>>tmp>>s[i],op[i]=(tmp=="Add")?1:-1; } for(int i=1;i<=m;i++){ string tmp;int l,r; cin>>tmp>>l>>r,ins(tmp); q[++cnt]={tmp,r,i,1},q[++cnt]={tmp,l,i,-1}; }sort(q+1,q+1+cnt);int j=1; for(int i=1;i<=cnt;i++){ while(j<=q[i].x)upd(s[j]),j++; res[q[i].id]+=q[i].op*qry(q[i].s); }for(int i=1;i<=m;i++)cout<<res[i]<<"\n"; }

[SCOI2016]背单词

首先有一个基本的事实: t r i e trie trie树上的祖先节点一定最先选择。

考虑自底向上合并。注意到按子树大小从小到大排序即可。答案是每一步合并操作的花费,也就是各节点代价之和。

#include<bits/stdc++.h> #define fi first #define se second #define ll long long #define pb push_back using namespace std; int n,trie[510005][26],vs[510005],sz[510005],tot,num; ll res; vector<int>ve[510005]; void ins(string s){ int it=0; for(int i=0;i<s.size();i++){ if(!trie[it][s[i]-'a'])trie[it][s[i]-'a']=++tot; it=trie[it][s[i]-'a']; }vs[it]=sz[it]=1; }void dfs(int u,int topf){ for(int i=0;i<26;i++){ int v=trie[u][i]; if(v){ dfs(v,vs[u]?u:topf),sz[u]+=sz[v]; } } if(vs[u]){ sort(ve[u].begin(),ve[u].end()); int tmp=1; for(int i=0;i<ve[u].size();i++){ res+=tmp,tmp+=ve[u][i]; }ve[topf].pb(sz[u]); } } int main(){ ios::sync_with_stdio(false); cin.tie(0),cout.tie(0); cin>>n; for(int i=1;i<=n;i++){ string tmp;cin>>tmp,reverse(tmp.begin(),tmp.end()),ins(tmp); } vs[0]=1,dfs(0,0),cout<<res; }

[Ynoi2011] 竞赛实验班

考察一个子问题。

给定一个序列 { ( a 1 ⊕ x ) ⊕ y , ( a 2 ⊕ x ) ⊕ y , . . . , ( a n ⊕ x ) ⊕ y } \{(a_1\oplus x)\oplus y,(a_2\oplus x)\oplus y,...,(a_n\oplus x)\oplus y\} {(a1x)y,(a2x)y,...,(anx)y},其中前一部分的 a i ⊕ x a_i\oplus x aix是有序的,问你区间 [ l , r ] [l,r] [l,r]的元素值之和。

对于有序的部分,在 trie \text{trie} trie树上二分 a i ⊕ x a_i\oplus x aix对应的有序元素中的 [ 1 , r ] [1,r] [1,r],通过记录每一位 0 0 0, 1 1 1的个数就能把异或后的和求出来。

对于无序的部分直接前缀和即可。

每次排序操作相当于把无序的序列合并到有序的序列上面去。

比较蛋疼的地方在于这样 trie \text{trie} trie树上还要统计每个子树中每一位 0 0 0, 1 1 1的数目,这样空间复杂度 O ( n log ⁡ 2 n ) O(n\log ^2n) O(nlog2n)无法通过。

注意到对于只有一个儿子的节点并不需要存储长度为 26 26 26的数组,这样每次加入时最多只会增加 1 1 1个节点,这样空间少一个 log ⁡ \log log

注意到这道题可以离线,那么我们每次单独考虑某一位,这样空间就是线性的了。

码码码。。。

我信仰什么,我便实现哪种方法。

#include<bits/stdc++.h> #define fi first #define se second #define ll long long #define pb push_back using namespace std; const int N=6200005; const int M=200005; int n,m,cnt; int trie[N][2],tot,ok; int a[M]; int d1,d2,s; int v1,v2,sz2[N][31]; ll sz[M][31]; ll sz1[N]; void ins(int x){ int it=0; for(int i=30;i>=0;i--){ int k=x>>i&1; if(!trie[it][k])trie[it][k]=++tot; it=trie[it][k],sz1[it]++; for(int j=0;j<=30;j++)sz2[it][j]+=(x>>j&1); } } void ins2(int x){ a[++v2]=x^d1^d2; for(int i=0;i<=30;i++)sz[v2][i]=sz[v2-1][i]+(a[v2]>>i&1); } ll qry(int x){ int it=0;ll num=0,tmp=0; for(int i=30;i>=0;i--){ int k=d1>>i&1; if(sz1[trie[it][k]]>=x){ it=trie[it][k],tmp+=k*(1ll<<i); } else { for(int j=0;j<=30;j++){ if((d1^d2)>>j&1)num+=(1ll<<j)*(sz1[trie[it][k]]-sz2[trie[it][k]][j]); else num+=(1ll<<j)*sz2[trie[it][k]][j]; } x-=sz1[trie[it][k]],it=trie[it][k^1],tmp+=(k^1)*(1ll<<i); } }for(int i=0;i<=30;i++){ if(((d1^d2)>>i&1)^(tmp>>i&1))num+=(1ll<<i)*x; } return num; } ll qry2(int x){ ll res=0; for(int i=0;i<=30;i++){ if((d1^d2)>>i&1){ res+=(1ll<<i)*(x-sz[x][i]); } else { res+=(1ll<<i)*sz[x][i]; } } return res; } signed main(){ ios::sync_with_stdio(false); cin.tie(0),cout.tie(0); cin>>n;for(int i=1;i<=n;i++){ int x;cin>>x,ins2(x); }cin>>m; for(int i=1;i<=m;i++){ int op,l,r,x; cin>>op; if(op==1){ cin>>x,ins2(x); } else if(op==2){ cin>>l>>r;ll res=0; if(r<=v1)res=qry(r)-qry(l-1); else if(l>v1)res=qry2(r-v1)-qry2(l-v1-1); else res=qry2(r-v1)+qry(v1)-qry(l-1); cout<<res<<"\n"; } else if(op==3){ cin>>x,d2^=x; } else { for(int j=1;j<=v2;j++)ins(a[j]); v1+=v2,v2=0,d1^=d2,d2=0; } } }

[UOJ176]新年的繁荣

巧妙的生成树题目。

首先对于相同的权值可以看成一个点。

然后考虑 kruskal \text{kruskal} kruskal算法。从大到小枚举边权 v a l = ( 0110...0 ) 2 val=(0110...0)_2 val=(0110...0)2,注意到能连的边不会超过 m m m条,那么我们对于每个 0 0 0的位置(假设第 i i i位)上找到一个点 u u u满足 ( u , v a l ∣ 2 i ) a n d = v a l ∣ 2 i (u,val|2^i)_{and}=val|2^i (u,val2i)and=val2i,然后把这些点连起来就行了。

怎么理解上述过程呢,注意到对于每个 0 0 0的位置(假设第 i i i位)如果 ( u , 2 i ) a n d ≠ 0 (u,2^i)_{and}\ne 0 (u,2i)and=0 并且 ( v , 2 i ) a n d ≠ 0 (v,2^i)_{and}\ne 0 (v,2i)and=0那么显然 ( u , v ) a n d > v a l (u,v)_{and}>val (u,v)and>val,事实上我们可以把这些位置分成若干组,其中每一组是封闭且联通的(与其他组的与运算都恰好是 v a l val val),那么边权 v a l val val的数目就是组数 − 1 -1 1

复杂度 O ( 2 m m ) O(2^mm) O(2mm)

#include<bits/stdc++.h> #define ll long long using namespace std; int n,m; int fa[1<<18],v[1<<18],v2[1<<18]; ll res; int find(int x){ return fa[x]==x?x:fa[x]=find(fa[x]); } int main(){ ios::sync_with_stdio(false); cin>>n>>m; for(int i=1;i<=n;i++){ int x;cin>>x; if(!v2[x])v[x]=x,v2[x]=1; else res+=x; } for(int i=(1<<m)-1;i>0;i--){ for(int j=0;j<m;j++){ if(!(i>>j&1)&&v[i|(1<<j)]){ v[i]=v[i|(1<<j)]; break; } } }for(int i=1;i<(1<<m);i++)fa[i]=i; for(int i=(1<<m)-1;i>0;i--){ int u=-1;if(v2[i])u=i; for(int j=0;j<m;j++){ if(!(i>>j&1)&&v[i+(1<<j)]){ int v2=v[i+(1<<j)]; if(u==-1)u=v2; else if(find(u)!=find(v2)){ fa[fa[v2]]=fa[u],res+=i; } } } }cout<<res; }

CF888G Xor-MST

考虑在 trie \text{trie} trie树上自底向上合并的过程。

暴力合并即可。复杂度 O ( n log ⁡ n log ⁡ A ) O(n\log n\log A) O(nlognlogA)

#include<bits/stdc++.h> #define ll long long using namespace std; int n,a[200005],sz[6000005],ed[6000005]; int trie[6000005][2],tot=1,tmp; ll res; void ins(int x){ int it=1; for(int i=29;i>=0;i--){ ed[it]=i; int k=x>>i&1;if(!trie[it][k])trie[it][k]=++tot; it=trie[it][k],sz[it]++; } } void dfs2(int u,int v,int w){ if(ed[u]==-1){tmp=min(tmp,w);return;} if(trie[u][0]){ if(trie[v][0])dfs2(trie[u][0],trie[v][0],w); else if(trie[v][1])dfs2(trie[u][0],trie[v][1],w+(1<<ed[u])); } if(trie[u][1]){ if(trie[v][1])dfs2(trie[u][1],trie[v][1],w); else if(trie[v][0])dfs2(trie[u][1],trie[v][0],w+(1<<ed[u])); } } void dfs(int u){ if(!u)return;int l=trie[u][0],r=trie[u][1]; dfs(l),dfs(r); if(l&&r){ if(sz[l]>sz[r])swap(l,r);tmp=1<<30,dfs2(l,r,1<<ed[u]),res+=tmp; } } int main(){ ios::sync_with_stdio(false); cin.tie(0),cout.tie(0); cin>>n,memset(ed,-1,sizeof ed); for(int i=1;i<=n;i++){ int x;cin>>x,ins(x); }dfs(1),cout<<res; }

__EOF__

本文作者仰望星空的蚂蚁
本文链接https://www.cnblogs.com/cqbzly/p/17530094.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   仰望星空的蚂蚁  阅读(14)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· DeepSeek 开源周回顾「GitHub 热点速览」
历史上的今天:
2021-10-20 【题解】[CSP-S2020] 函数调用
2021-10-20 【题解】I love random (计数 dp)
点击右上角即可分享
微信分享提示