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