Trie 字典树

Trie字典树

是一种字符串的存储结构,节省空间,同时可以查询是否有字符串是其前缀,也是异或xor操作的利器。

POJ 3630 Phone List 模板:

#include<algorithm>
#include<cstring>
#include<cstdio>
const int MAXN=2e5,MAXL=26;
int t,n,tot;
int ch[MAXN+10][MAXL+10];
bool ed[MAXN+10];
char s[MAXN+10];
void Init() {
	memset(ch,0,sizeof(ch));
	memset(ed,0,sizeof(ed));
	tot=1;
}
bool Insert(char *s) {
	int len=strlen(s);
	int u=1;
	bool flag=false;
	for(int i=0; i<len; i++) {
		int c=s[i]-'0';
		if(!ch[u][c]) ch[u][c]=++tot;
		else if(i==len-1) flag=true;
		u=ch[u][c];
		if(ed[u]) flag=true;
	}
	ed[u]=true;
	return flag;
}
int main() {
	scanf("%d",&t);
	for(int r=1; r<=t; r++) {
		scanf("%d",&n);
		Init();
		bool ans=false;
		for(int i=1; i<=n; i++) {
			scanf("%s",s);
			if(Insert(s)) ans=true;
		}
		if(ans) puts("NO");
		else puts("YES");
	}
}

ed数组是代表有字符串在这里结束,注意不能用关键字end
这是一道字符串统计题。
当一个字符串到最后都没有新建节点或经过了结束标记,那么就是存在前缀了。

模板The XOR Largest Pair

将所有数转化为二进制存入字典树。
以一个数为基准,可以求出与它异或最大值。
把这个数转化为二进制,从高位到低位,每次走与它值相反的数(不同为1,相同为0),如果没有,则不走。

#include<algorithm>
#include<cstdio>
#include<cstring>
using namespace std;
const int MAXN=1e5;
int n,a[MAXN+10],c[MAXN*32+10][2],ends[MAXN*32+10],tot=1;
int b[MAXN+10][32],res;
void Insert(int val,int id) {
	int u=1;
	for(int i=30; i>=0; i--) {
		int k;
		if(val-(1<<i)>=0) {
			k=1;
			val-=(1<<i);
		} else k=0;
		if(!c[u][k]) c[u][k]=++tot;
		u=c[u][k];
		b[id][i]=k;
	}
	ends[u]=id;
}
int solve(int id) {
	int u=1;
	for(int i=30; i>=0; i--) {
		int k=1-b[id][i];
		if(c[u][k]) u=c[u][k];
		else u=c[u][1-k];
	}
	return a[id]^a[ends[u]];
}
int main() {
	scanf("%d",&n);
	for(int i=1; i<=n; i++) {
		scanf("%d",&a[i]);
		Insert(a[i],i);
	}
	for(int i=1; i<=n; i++) {
		res=max(res,solve(i));
	}
	printf("%d\n",res);
	return 0;
}

Nikitosh 和异或

我们知道,\(a_s⊕a_{s+1}⊕...⊕a_t=(a_1⊕...⊕a_t)⊕(a_1⊕...⊕a_{s-1})\).
所以我们只要前缀数组,即可求出区间的异或和。
即把所有前缀和循环加入字典树,然后如同上题一般可以求出对于最大值,但这只是对于\(a_1\)~\(a_i\)中的最大的区间异或和而已。
同理,我们倒序循环,可以求出\(a_n\)~\(a_i\)的最大区间异或和。
最后,我们把这两个结合起来。

#include<algorithm>
#include<cstdio>
#include<cstring>
using namespace std;
const int MAXN=4e5;
int n,tot=1;
int ch[MAXN*32+10][2],end[MAXN*32+10];
int l[MAXN+10],r[MAXN+10],a[MAXN+10],ans;
void Insert(int x) {
	int u=1;
	for(int i=30; i>=0; i--) {
		int c;
		if(x-(1<<i)>=0) {
			x-=1<<i;
			c=1;
		} else c=0;
		if(!ch[u][c]) ch[u][c]=++tot;
		u=ch[u][c];
	}
}
int Find(int x) {
	int u=1,ans=0;
	for(int i=30; i>=0; i--) {
		int c;
		if(x-(1<<i)>=0) {
			x-=1<<i;
			c=1;
		} else c=0;
		if(ch[u][1-c]) {
			u=ch[u][1-c];
			ans+=(1<<i);
		}
		else u=ch[u][c];
	}
	return ans;
}
int main() {
	Insert(0);
	scanf("%d",&n);
	for(int i=1; i<=n; i++)
		scanf("%d",&a[i]);
	for(int i=1,sum=0; i<=n; i++) {
		sum^=a[i];
		Insert(sum);
		l[i]=max(l[i-1],Find(sum));
	}
	memset(ch,0,sizeof(ch));
	tot=1; Insert(0);
	for(int i=n,sum=0; i>=1; i--) {
		sum^=a[i];
		Insert(sum);
		r[i]=max(r[i+1],Find(sum));
	}
	for(int i=1; i<=n; i++)
		ans=max(ans,l[i]+r[i+1]);
	printf("%d\n",ans);
	return 0;
}

这里为什么要Insert(0)呢?
因为这样才能保证可以把\(a_1\)~\(a_n\)记录进答案。
前缀\(sum_i⊕0=0\)

Secret Message 秘密信息

\(sum\)数组存储字典树中该节点经过个数,\(ed\)数组表示该节点有多少个结束标记。
答案是经过节点(除了结束节点)\(ed\) 之和 + 结束节点的\(sum\)

#include<algorithm>
#include<cstdio>
#include<iostream>
using namespace std;
const int MAXN=1e7;
int n,m,ch[MAXN+10][2],tot=1,ed[MAXN+10],sum[MAXN+10];
int main() {
	scanf("%d %d",&n,&m);
	for(int i=1,len; i<=n; i++) {
		scanf("%d",&len);
		int u=1;
		for(int j=1,x; j<=len; j++) {
			scanf("%d",&x);
			if(!ch[u][x]) ch[u][x]=++tot;
			u=ch[u][x];
			sum[u]++;
		}
		ed[u]++;
	}
	for(int i=1,len; i<=m; i++) {
		scanf("%d",&len);
		int u=1,ans=0;
		for(int j=1,x; j<=len; j++) {
			scanf("%d",&x);
			if(!ch[u][x]) u=tot+1;
			u=ch[u][x];
			ans+=ed[u];
		}
		printf("%d\n",ans+sum[u]-ed[u]);
	}
	return 0;
}

The XOR-longest Path

树上异或路径等于两个节点到根节点异或和异或起来。

把所有根节点到节点的异或和算出,存入字典树,计算其中最大值揭示答案。
如同Nikitosh 和异或,需要Insert(0)

#include<algorithm>
#include<cstdio>
#include<iostream>
using namespace std;
const int MAXN=2e5,DEP=30;
int n,tot,ver[MAXN+10],nxt[MAXN+10],edge[MAXN+10],head[MAXN+10];
int ch[DEP*MAXN+10][2],cnt=1,num=0,rec[MAXN+10],ans;
void Addedge(int x,int y,int z) {
	ver[++tot]=y;
	edge[tot]=z;
	nxt[tot]=head[x];
	head[x]=tot;
}
void Insert(int x) {
	rec[++num]=x;
	int u=1;
	for(int i=DEP; i>=0; i--) {
		int c;
		if(x-(1<<i)>=0) {
			c=1;
			x-=1<<i;
		} else c=0;
		if(!ch[u][c]) ch[u][c]=++cnt;
		u=ch[u][c];
	}
}
void DFS(int u,int father,int val) {
	for(int i=head[u]; i; i=nxt[i]) {
		int v=ver[i],z=edge[i];
		if(v!=father) {
			val=val^z;
			Insert(val);
			DFS(v,u,val);
			val=val^z;
		}
	}
}
int Query(int x) {
	int u=1,res=0;
	for(int i=30; i>=0; i--) {
		int c;
		if(x-(1<<i)>=0) {
			c=1;
			x-=1<<i;
		} else c=0;
		if(!ch[u][!c]) u=ch[u][c];
		else {
			u=ch[u][!c];
			res+=(1<<i);
		}
	}
	return res;
}
int main() {
	scanf("%d",&n);
	for(int i=1,u,v,w; i<n; i++) {
		scanf("%d %d %d",&u,&v,&w);
		Addedge(u,v,w);
		Addedge(v,u,w);
	}
	Insert(0);
	DFS(1,0,0);
	for(int i=1; i<=num; i++)
		ans=max(ans,Query(rec[i]));
	printf("%d\n",ans);
	return 0;
}
posted @ 2022-08-13 13:33  s1monG  阅读(19)  评论(0编辑  收藏  举报