前言

能力有限,只会最浅的.

一、Trie

常识

字典树空间复杂度: O ( N × ∑ ) O(N\times \sum) O(N×) N N N是所有串长度之和, ∑ \sum ​​ 是字符集大小。

定义Trie

struct Trie{
    int son[M];bool ed=false; //这里的M就是字符集大小
}a[N]; 	//N就是所有串长度之和
int tot; //用来记录结点个数

a [ u ] . s o n [ c ] a[u].son[c] a[u].son[c] 表示结点 u u u的儿子 c c c的编号。

a [ u ] . e d a[u].ed a[u].ed 用来判断该结点是否为某个字符串的尾结点。

插入字符串

void ins(char *s){
	int u=0,l=strlen(s+1);
    for(int i=1;i<=l;i++){
        int c=s[i]-'a';
		if(!a[u].son[c]) a[u].son[c]=++tot;
        u=a[u].son[c];
    }
    a[u].ed=true;
}

查询某个字符串是否出现

bool query(char *s){
	int u=0,l=strlen(s+1);
    for(int i=1;i<=l;i++){
        int c=s[i]-'a';
		if(!a[u].son[c]) return false;
        u=a[u].son[c];
    }
    return true;	
}

HDU1671

判断是否有一串是另一串前缀。

插入时判断是否出现结尾标记或者结尾处仍有儿子。

时间复杂度: O ( n l ) O(nl) O(nl) l l l为单个字符串长度​

// Problem: UVA11362 Phone List
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/UVA11362
// Memory Limit: 0 MB
// Time Limit: 5000 ms
// Date: 2021-07-28 09:18:07
// --------by Herio--------

#include<iostream>
#include<cstdio>
#include<string>
#include<cstring>
#include<vector>
#include<algorithm>
using namespace std;
typedef long long ll;
typedef unsigned long long ull; 
const int N=1e4+5,M=1e4+5,inf=0x3f3f3f3f,mod=1e9+7;
#define mst(a,b) memset(a,b,sizeof a)
#define PII pair<int,int>
#define fi first
#define se second
#define pb emplace_back
#define SZ(a) (int)a.size()
#define IOS ios::sync_with_stdio(false),cin.tie(0) 
void Print(int *a,int n){
	for(int i=1;i<n;i++)
		printf("%d ",a[i]);
	printf("%d\n",a[n]); 
}
struct Trie{
	int son[10],ed=0;
}a[N];
int ok,tot;
char s[11];
void ins(char *s){
	int u=0;
	for(int i=1;s[i];i++){
		int c=s[i]-'0';
		if(!a[u].son[c]) a[u].son[c]=++tot;
		u=a[u].son[c];
		if(a[u].ed) ok=1;
	}
	for(int i=0;i<10;i++) if(a[u].son[i]) ok=1;
	a[u].ed=1;
}
int main(){
	int t;
	scanf("%d",&t);
	while(t--){
		int n;scanf("%d",&n);ok=tot=0;mst(a,0);
		for(int i=0;i<n;i++){
			scanf("%s",s+1);
			ins(s);
		}
		puts(ok?"NO":"YES");
 	}
	return 0;
}

P2292 [HNOI2004]L语言

对单词倒着建Trie树,然后维护 f f f数组, f i f_i fi表示前缀 i i i是否可行。

然后就是普通dp了。

// Problem: P2292 [HNOI2004]L语言
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P2292
// Memory Limit: 128 MB
// Time Limit: 1000 ms
// Date: 2021-07-28 10:22:23
// --------by Herio--------

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull; 
const int N=2e6+5,M=2e4+5,inf=0x3f3f3f3f,mod=1e9+7;
#define mst(a,b) memset(a,b,sizeof a)
#define PII pair<int,int>
#define fi first
#define se second
#define pb emplace_back
#define SZ(a) (int)a.size()
#define IOS ios::sync_with_stdio(false),cin.tie(0) 
void Print(int *a,int n){
	for(int i=1;i<n;i++)
		printf("%d ",a[i]);
	printf("%d\n",a[n]); 
}
char buf[1<<21],*p1=buf,*p2=buf;
template <typename T>
inline T& read(T& r) {
    r = 0; bool w = 0; char ch = getchar();
    while(ch < '0' || ch > '9') w = ch == '-' ? 1 : 0, ch = getchar();
    while(ch >= '0' && ch <= '9') r = r * 10 + (ch ^ 48), ch = getchar();
    return r = w ? -r : r;
}
#define il inline 
struct Trie{
	int s[26];bool ed=false;
}a[205];
int n,m,tot;
il void ins(char *s){
	int u=0,l=strlen(s+1);
	for(int i=l;i;i--){
		int c=s[i]-'a';
		if(!a[u].s[c]) a[u].s[c]=++tot;
		u=a[u].s[c];
	}
	a[u].ed=true;
}
char s[12],t[N];
bool f[N];
int main(){
	read(n),read(m);
	while(n--){
		scanf("%s",s+1);ins(s);
	}
	while(m--){
		f[0]=true;
		scanf("%s",t+1);int l=strlen(t+1);
		for(int i=1;i<=l;i++){
			f[i]=false;int u=0;
			for(int j=i;j;j--){
				int c=t[j]-'a';
				if(!a[u].s[c]) break;
				u=a[u].s[c];
				if(a[u].ed){
					f[i]|=f[j-1];
					if(f[i]) break;
				}
			}
		}
		int j;
		for(j=l;j;j--) if(f[j]) break;
		printf("%d\n",j);
	}
	return 0;
}

P2922 [USACO08DEC]Secret Message G

给定若干模式串,然后问每个文本串与多少个模式串前缀匹配,前缀就是两者长度较小的字符串。

对模式串建Trie,维护每个结点经过的次数和结尾标记。

然后就比较简单了,注意特判下比该文本串长的模式串个数。

// Problem: P2922 [USACO08DEC]Secret Message G
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P2922
// Memory Limit: 125 MB
// Time Limit: 1000 ms
// Date: 2021-07-06 15:48:40
// --------by Herio--------

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull; 
const int N=5e4+5,M=5e5+5,inf=0x3f3f3f3f,mod=1e9+7;
#define mst(a,b) memset(a,b,sizeof a)
#define PII pair<int,int>
#define fi first
#define se second
#define pb emplace_back
#define SZ(a) (int)a.size()
#define IOS ios::sync_with_stdio(false),cin.tie(0) 
void Print(int *a,int n){
	for(int i=1;i<n;i++)
		printf("%d ",a[i]);
	printf("%d\n",a[n]); 
}
struct node{
	int nt[2],s,ed;
}a[M];
int b[N],len,cnt=1;
void ins(){
	int p=1;
	for(int j=1;j<=len;j++){
		if(!a[p].nt[b[j]]) a[p].nt[b[j]]=++cnt;
		p=a[p].nt[b[j]];
		a[p].s++;
	}
	a[p].ed++;
}
ll que(){
	ll ans=0;
	int p=1;
	bool ok=true;
	for(int i=1;i<=len;i++){
		if(!a[p].nt[b[i]]){
			ok=0;break;
		} 
		p=a[p].nt[b[i]];
		ans+=a[p].ed;
	}
	return ok?ans+a[p].s-a[p].ed:ans;
}
int main(){
	int n,m;scanf("%d%d",&m,&n);
	for(int i=1;i<=m;i++){
		scanf("%d",&len);
		for(int j=1;j<=len;j++) scanf("%d",&b[j]);
		ins();
	}
	for(int i=1;i<=n;i++){
		scanf("%d",&len);
		for(int j=1;j<=len;j++) scanf("%d",&b[j]);
		printf("%lld\n",que());
	}
	return 0;
}

二、01-Trie

https://harris.blog.csdn.net/article/details/107586527

我的上面这篇文章讲过,这里再回顾一下。

01-Trie还是利用Trie+贪心。

从高位往低位建Trie

P4551 最长异或路径

dfs预处理出根到其他点异或和 f i f_i fi,然后就是01-Trie 的裸题了.

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long ll;
const int N=1e5+5,M=3e6+5,inf=0x3f3f3f3f,mod=1e9+7;
#define mst(a) memset(a,0,sizeof a)
#define lx x<<1
#define rx x<<1|1
#define reg register
#define PII pair<int,int>
#define fi first
#define se second
#define pb push_back
int n,son[M][2],f[N],h[N],cnt,tot;
struct node{
	int to,nt,w;
}e[N<<1];
void add(int u,int v,int w){
	e[++tot]={v,h[u],w},h[u]=tot;
	e[++tot]={u,h[v],w},h[v]=tot;
}
void dfs(int u,int fa){
	for(int i=h[u];i;i=e[i].nt){
		 if(e[i].to==fa) continue;
		 int v=e[i].to;
		 f[v]=f[u]^e[i].w;
		 dfs(v,u);
	}
}
void ins(int x){
	int p=0;
	for(int i=30;~i;i--){
		int &s=son[p][x>>i&1];
		if(!s) s=++cnt;
		p=s;
	}
}
int que(int x){
	int p=0,ans=0;
	for(int i=30;~i;i--){
		int s=x>>i&1;
		if(son[p][!s]) ans+=1<<i,p=son[p][!s];
		else p=son[p][s];
	} 
	return ans; 
}
int main(){
	scanf("%d",&n);
	for(int i=1,u,v,w;i<n;i++){
		scanf("%d%d%d",&u,&v,&w);
		add(u,v,w);
	}
	dfs(1,0);int ans=0;
	for(int i=1;i<=n;i++){
		ans=max(ans,que(f[i]));
		ins(f[i]);
	}
	printf("%d\n",ans);
	return 0;
	
}

P4735 最大异或和

结尾插入结点,查询区间最大异或和。

可持久化01-Trie

每个前缀异或和维护一棵Trie。

查询就是第 [ l − 1 , r − 1 ] [l-1,r-1] [l1,r1]的前缀和异或和与 s u m ⊕ x sum\oplus x sumx的最大异或值。

为了方便,我们把所有位置右移动。

先插入 0 0 0这个值作为第一棵树,这样 [ l − 1 , r − 1 ] → [ l , r ] [l-1,r-1]\rightarrow [l,r] [l1,r1][l,r]

又因为是差分的性质,我们查询 [ l , r ] [l,r] [l,r]区间的树,所以要访问区间 [ l − 1 , r ] [l-1,r] [l1,r]​​。

空间要开 2 e 7 2e7 2e7

具体见code

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull; 
const int N=2e7+5,M=2e4+5,inf=0x3f3f3f3f,mod=1e9+7;
#define mst(a,b) memset(a,b,sizeof a)
#define PII pair<int,int>
#define fi first
#define se second
#define pb emplace_back
#define SZ(a) (int)a.size()
#define IOS ios::sync_with_stdio(false),cin.tie(0) 
void Print(int *a,int n){
	for(int i=1;i<n;i++)
		printf("%d ",a[i]);
	printf("%d\n",a[n]); 
}
char buf[1<<21],*p1=buf,*p2=buf;
template <typename T>
inline T& read(T& r) {
    r = 0; bool w = 0; char ch = getchar();
    while(ch < '0' || ch > '9') w = ch == '-' ? 1 : 0, ch = getchar();
    while(ch >= '0' && ch <= '9') r = r * 10 + (ch ^ 48), ch = getchar();
    return r = w ? -r : r;
}
int n,q,sum,s[N][2],sz[N],rt[N],tot,cnt;
inline void ins(int x){
	int p=rt[cnt];//上一棵Trie 的根结点编号p
	rt[++cnt]=++tot;//新建一棵树的根结点,编号为tot
	for(int i=23;~i;i--){
		int c=x>>i&1;
		sz[tot]=sz[p]+1;//在前一棵树的基础上更新树大小.
		s[tot][!c]=s[p][!c];//另一个子树直接继承上一个版本.
		s[tot][c]=tot+1;++tot;//新建结点,注意这里不要缩写为一步,优先级问题.
		p=s[p][c];//上一颗树继续往下走.
	}
	sz[tot]=sz[p]+1;//结尾的size更新.
}
inline void que(int l,int r,int x){
	int lc=rt[l],rc=rt[r],ans=0;//找到第lc棵树,第rc棵树.
	for(int i=23;~i;i--){
		int c=x>>i&1;
		//如果还有结点往该放方向走
		if(sz[s[rc][!c]]>sz[s[lc][!c]]) ans^=1<<i,lc=s[lc][!c],rc=s[rc][!c];
		else lc=s[lc][c],rc=s[rc][c];
	}
	printf("%d\n",ans);return;
}
int main(){
	read(n),read(q);ins(0);//要插入0,rt[1]=1,0是第一棵树.
	for(int i=0,x;i<n;i++)
		read(x),sum^=x,ins(sum);
	while(q--){
		char ch;
		scanf("\n%c",&ch);
		if(ch=='A'){
			int x;read(x),sum^=x,ins(sum);
		}
		else {
			int l,r,x;//注意询问的查询的rt[l]是第l-1棵树,因为0是第一棵树.
			//所以此时的区间[l-2,r-1]
			read(l),read(r),read(x);
			que(l-1,r,sum^x);
		}
	}
	return 0;
}

P5283 [十二省联考2019]异或粽子

求前 k k k大区间异或和 的和.

01-Trie+大根堆

先把上三角转换为矩形,相同的异或不管,因为值为0,不会影响结果.

预处理出每个前缀异或和 s i s_i si与整个区间的最大异或和,丢进大根堆.

然后每次取最大的,加上贡献,再求该 s i s_i si与整个区间第 r k + 1 rk+1 rk+1大,丢进大根堆.

注意求到 r k = n rk=n rk=n 就不要再丢进了.

时间复杂度: O ( ( n + k ) l o g a l o g n ) O((n+k)loga logn) O((n+k)logalogn)

// Problem: P5283 [十二省联考2019]异或粽子
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P5283
// Memory Limit: 1 MB
// Time Limit: 3500 ms
// Date: 2021-07-28 15:37:35
// --------by Herio--------

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull; 
const int N=5e5+5,M=2e4+5,inf=0x3f3f3f3f,mod=1e9+7;
#define mst(a,b) memset(a,b,sizeof a)
#define PII pair<int,int>
#define fi first
#define se second
#define pb emplace_back
#define SZ(a) (int)a.size()
#define IOS ios::sync_with_stdio(false),cin.tie(0) 
void Print(int *a,int n){
	for(int i=1;i<n;i++)
		printf("%d ",a[i]);
	printf("%d\n",a[n]); 
}
//if have char input #define should cancel
#define getchar()(p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
char buf[1<<21],*p1=buf,*p2=buf;
template <typename T>
inline T& read(T& r) {
    r = 0; bool w = 0; char ch = getchar();
    while(ch < '0' || ch > '9') w = ch == '-' ? 1 : 0, ch = getchar();
    while(ch >= '0' && ch <= '9') r = r * 10 + (ch ^ 48), ch = getchar();
    return r = w ? -r : r;
}
int n,k,s[N*30][2],sz[N*30],tot;
inline void ins(ll x){
	int u=0;
	for(int i=31;~i;i--){
		int c=x>>i&1;
		if(!s[u][c]) s[u][c]=++tot;
		u=s[u][c];
		sz[u]++;
	}
}
inline ll que(ll x,int k){
	ll ans=0;int u=0;
	for(int i=31;~i;i--){
		int c=x>>i&1;
		if(sz[s[u][!c]]>=k) ans^=(1LL<<i),u=s[u][!c];
		else k-=sz[s[u][!c]],u=s[u][c];
	}
	return ans;
}
ll a[N];
struct node{
	int id,rk;ll v;
	bool operator<(const node &a)const{
		return v<a.v;
	}
};
priority_queue<node>q;
int main(){
	read(n),read(k),ins(0);k<<=1;
	for(int i=1;i<=n;i++)
		read(a[i]),a[i]^=a[i-1],ins(a[i]);
	for(int i=0;i<=n;i++) q.push({i,1,que(a[i],1)});
	ll ans=0;
	while(k--){
		node u=q.top();q.pop();
		ans+=u.v;
		if(u.rk<n) q.push({u.id,u.rk+1,que(a[u.id],u.rk+1)});
	}
	printf("%lld\n",ans>>1);
	return 0;
}

此题还有个不依赖 k k k​​的解法, 见CF241B Friends,还不会.