【学习笔记】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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值