字典树(trie):或名前缀树,哈希树的变种,大多题目(非水题)与哈希树套用求解。矮+胖为其显著特征,以空间换时间的典例。
通过利用字符串的公共前缀可实现字符串的快速查询。
板子如下:
#include <bits/stdc++.h>
using namespace std;
const int maxn=2e6+5;
int tot=0;
int tree[maxn][30];
int flag[maxn];
void add(char *s)
{
int len=strlen(s);
int root=0;
for(int i=0;i<len;i++)
{
int id=s[i]-'a';
if(!tree[root][id]) tree[root][id]=++tot;
root=tree[root][id];
}
flag[root]=1;
}
bool fid(char *s)
{
int len=strlen(s);
int root=0;
for(int i=0;s[i];i++)
{
int id=s[i]-'a';
if(tree[root][id]==0)
{
return 0;
}
root=tree[root][id];
}
return flag[root];
}
/*
删除操作:
三种可能:
1、没找到该单词
2、找到叶节点时、叶节点的cnt标志清零,代表该节点不是叶节点
3、当前节点没有其他的孩子节点时,可直接删除该节点
*/
void del(char *str,int word)
{
int len=strlen(str);
int root=0;
if(word==0)//未找到
{
return ;
}
for(int i=0;i<len;i++)
{
int id=str[i]-'a';
if(tree[root][id]==0)//没有子节点
return;
sum[root]-=cnt;
root=tree[root][id];
}
sum[root]=0;
for(int i=0;i<26;i++)
{
tree[root][id]=0;
}
}
struct Node{
int sum;//前缀
int next[26];//子节点
void init(){
sum=0;
memset(next,-1,sizeof next);
}
}tire[N];
int tot;
void add(char *str){
int len=strlen(str);
int root=0;
for(int i=0;i<len;i++){
int x=str[i]-'a';
if(tire[root].next[x]==-1)
tire[root].next[x]=tot++;
root=tire[root].next[x];
tire[root].sum++;
}
}
int fid(char *str){
int len=strlen(str);
int root=0;
for(int i=0;i<len;i++){
int x=str[i]-'a';
if(tire[root].next[x]==-1)
return 0;
root=tire[root].next[x];
}
return tire[root].sum;
}
void del(char *str,int word){
int len=strlen(str);
int root=0;
if(word<0)
return;
for(int i=0;i<len;i++){
int x=str[i]-'a';
if(tire[root].next[x]==-1)
return;
tire[root].sum-=word;
root=tire[root].next[x];
}
tire[root].sum=0;
for(int i=0;i<26;i++)
tire[root].next[i]=-1;
}
int main(){
tot=1;
for(int i=0;i<N;i++)
tire[i].init();
int t;
scanf("%d",&t);
while(t--){
char str[10],word[35];
scanf("%s%s",str,word);
if(str[0]=='i')//插入
add(word);
else if(str[0]=='d')//删除
del(word,fid(word));
else{//查询
if(fid(word)>0)
printf("Yes\n");
else
printf("No\n");
}
}
return 0;
}
//前缀出现次数
int trie[400001][26],tot;
int sum[400001];
void add(char *s){
int root=0;
int len=strlen(s);
for(int i=0;i<len;i++){
int id=s[i]-'a';
if(!trie[root][id])
trie[root][id]=++tot;
sum[trie[root][id]]++;//前缀保存
root=trie[root][id];
}
}
int fid(char *s){
int root=0;
int len=strlen(s);
for(int i=0;i<len;i++){//root经过循环后变成前缀最后一个字母所在位置
int id=s[i]-'a';
if(!trie[root][id])
return 0;
root=trie[root][id];
}
return sum[root];
}
int main(){
int n,m;
char s[11];
scanf("%d",&n);//插入单词个数
for(int i=1;i<=n;i++){
scanf("%s",s);
add(s);
}
scanf("%d",&m);//查询次数
for(int i=1;i<=m;i++){
scanf("%s",s);
printf("%d\n",add(s));
}
}
//单词|前缀是否出现
int tot=1;
int trie[N][26];//trie[rt][x]=tot,root是上个节点编号,x是字母,tot是下个节点编号
//bool vis[N];//查询整个单词用
void add(char *s,int root){
for(int i=0;s[i];i++){
int x=s[i]-'a';
if(trie[root][x]==0)//现在插入的字母在之前同一节点处未出现过
trie[root][x]=++tot;//字母插入一个新的位置,否则不做处理
root=trie[root][x];//为下个字母的插入做准备
}
//vis[root]=true;//标志该单词末位字母的尾结点,在查询整个单词时用
}
bool add(char *s,int root){
for(int i=0;s[i];i++){
int x=s[i]-'a';
if(trie[rt][x]==0)
return false;//以rt为头结点的x字母不存在,返回0
rt=trie[rt][x];//为查询下个字母做准备
}
return true;
//return vis[rt];//查询整个单词时
}
int main(){
int n,m;
char s[22];
tot=0;
scanf("%d",&n);//插入单词个数
for(int i=1;i<=n;i++){
scanf("%s",s);
add(s,1);
}
scanf("%d",&m);//查询单词个数
for(int i=1;i<=n;i++){
scanf("%s",s);
if(fid(s,1))
printf("YES\n");
else
printf("NO\n");
}
return 0;
}
例题:
A:
板子题:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
using namespace std;
const int maxn=2e6+5;
int tree[maxn][30],tot=0;//26¸ö×Öĸ
bool flag[maxn];
int sum[100007];
char a[100007];
typedef long long ll;
void add(char *s)
{
int root=0,id,len=strlen(s);
for(int i=0;i<len;++i)
{
id=s[i]-'a';
if(!tree[root][id]) tree[root][id]=++tot;
sum[tree[root][id]]++;
root=tree[root][id];
}
//flag[root]=true;
}
ll fid(char *s)
{
//long long res=0;
int root=0,id,len=strlen(s);
for(int i=0;i<len;++i)
{
id=s[i]-'a';
if(!tree[root][id]) return 0;
root=tree[root][id];
}
return sum[root];
//if(flag[root]) return 1;
/*else
return 0;*/
}
int main()
{
int n;
scanf("%d",&n);
while(n--)
{
scanf("%s",a);
add(a);
}
int m;
scanf("%d",&m);
while(m--)
{
scanf("%s",a);
printf("%lld\n",fid(a));
}
return 0;
}
B:
思路:有趣题,可通过建双trie储存字符串拼接对比得到结果。
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<string>
#include<cstring>
#include<queue>
#include<stack>
#include<map>
#include<set>
#include<cmath>
#include<sstream>
using namespace std;
typedef long long ll;
const ll inf=0x3f3f3f3f;
const int maxn=2e6+5;
int tree1[maxn][30],tree2[maxn][30];
bool flag1[maxn],flag2[maxn];
int vis[maxn];
int tot1=0,tot2=0;
void add1(char *s){
int root=0,id,len=strlen(s);
for(int i=0;i<len;++i){
id=s[i]-'a';
if(!tree1[root][id]) tree1[root][id]=++tot1;
root=tree1[root][id];
}
flag1[root]=true;
}
void add2(char *s){
int root=0,id,len=strlen(s);
for(int i=0;i<len;++i){
id=s[i]-'a';
if(!tree2[root][id]) tree2[root][id]=++tot2;
root=tree2[root][id];
}
flag2[root]=true;
}
void find1(char *s){
int root=0,id,len=strlen(s);
for(int i=0;i<len;++i){
id=s[i]-'a';
root=tree1[root][id];
if(flag1[root]) vis[i]++;
}
}
void find2(char *s){
int root=0,id,len=strlen(s);
for(int i=0;i<len;++i){
id=s[i]-'a';
root=tree2[root][id];
if(flag2[root]) vis[len-i-2]++;
}
}
char a[50005][100];
set<string> ans;
int main(){
int cnt=0;
while(scanf("%s",a[cnt++])!=EOF){
add1(a[cnt-1]);
strrev(a[cnt-1]);
add2(a[cnt-1]);
strrev(a[cnt-1]);
}
for(int i=0;i<cnt;++i){
int len=strlen(a[i]);
for(int j=0;j<len;++j) vis[j]=0;
find1(a[i]);
strrev(a[i]);
find2(a[i]);
strrev(a[i]);
for(int j=0;j<len;++j){
if(vis[j]>=2){
string tp=a[i];
ans.insert(tp);
break;
}
}
}
for(auto it=ans.begin();it!=ans.end();++it){
cout<<*it<<endl;
}
return 0;
}
C:HDU-1251
思路:和A类似,板子题
#include <bits/stdc++.h>
using namespace std;
const int maxn=2e6+5;
int tree[maxn][30],tot=0;//26¸ö×Öĸ
bool flag[maxn];
int sum[100007];
char a[100007];
typedef long long ll;
void add(char *s)
{
int root=0,id,len=strlen(s);
for(int i=0;i<len;++i)
{
id=s[i]-'a';
if(!tree[root][id]) tree[root][id]=++tot;
sum[tree[root][id]]++;
root=tree[root][id];
}
//flag[root]=true;
}
ll fid(char *s)
{
//long long res=0;
int root=0,id,len=strlen(s);
for(int i=0;i<len;++i)
{
id=s[i]-'a';
if(!tree[root][id]) return 0;
root=tree[root][id];
}
return sum[root];
//if(flag[root]) return 1;
/*else
return 0;*/
}
int main()
{
while(gets(a))
{
if(a[0]=='\0') break;
add(a);
}
char b[maxn];
while(scanf("%s",b)!=EOF)
{
printf("%lld\n",fid(b));
}
return 0;
}
D:Immediate Decodability HDU-1305
思路:字典树的第二姿势,即对前缀的查询,稍微改改板子。若有字符串作为其他字符串的前缀,即为可消除的,也就是有一个串上非末尾阶段有flag标记。
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<string>
#include<cstring>
#include<queue>
#include<stack>
#include<map>
#include<set>
#include<cmath>
#include<sstream>
using namespace std;
typedef long long ll;
const ll inf=0x3f3f3f3f;
const int maxn=2e6+5;
int tree[maxn][15],sum[maxn],tot=0;
char a[10005][15];
int T,n;
void add(char *s) {
int root=0,id,len=strlen(s);
for(int i=0; i<len; ++i) {
id=s[i]-'0';
if(!tree[root][id]) tree[root][id]=++tot;
root=tree[root][id];
sum[root]++;
}
}
bool fid(char *s) {
int root=0,id,len=strlen(s),tp=0;
for(int i=0; i<len; ++i) {
id=s[i]-'0';
root=tree[root][id];
if(sum[root]>=2) tp++;
}
if(tp==len) return false;
else return true;
}
int main()
{
int k=1;
int cnt=0;
while(scanf("%s",a[cnt])!=EOF){
if(a[cnt][0]!='9'){
add(a[cnt]);
cnt++;
}
else{
bool pd=0;
for(int i=0;i<cnt;i++){
if(!fid(a[i])){
pd=1;
break;
}
}
if(!pd) printf("Set %d is immediately decodable\n",k++);
else printf("Set %d is not immediately decodable\n",k++);
cnt=0;
for(int i=0;i<=tot;++i){
for(int j=0;j<10;++j){
tree[i][j]=0;
}
}
}
}
return 0;
}
E : What Are You Talking About HDU - 1075
用map做的,这里就不开了,到时候开map的时候会拿出来写的。
F:Phone List poj-3630
在poj上6w多KB过的,学长搬过来卡内存,就不会了。
也就gg了17发而已。
G: Shortest Prefixes POJ - 2001
思路:每个每个去找最大的没有flag的地方。
#include <iostream>
#include <cstdio>
#include <string>
#include <cstring>
using namespace std;
const int maxn=2e6+5;
int tree[maxn][30],tot=0;//26¸ö×Öĸ
bool flag[maxn];
int sum[100007];
typedef long long ll;
void add(char *s)
{
int root=0,id,len=strlen(s);
for(int i=0; i<len; ++i)
{
id=s[i]-'a';
if(!tree[root][id]) tree[root][id]=++tot;
root=tree[root][id];
sum[root]++;
}
flag[root]=1;
}
string fid(char *s)
{
string ans=""; //long long res=0;
int root=0,id,len=strlen(s);
for(int i=0; i<len; ++i)
{
id=s[i]-'a';
root=tree[root][id];
ans+=s[i];
if(sum[root]==1) return ans;
}
return ans;
}
char a[1007][30];
int main()
{
int j=0;
int cot=0;
while(scanf("%s",a[j++])!=EOF)
{
//if(cot==12) break;
add(a[j-1]);
//cot++;
//printf("1\n");
}
//printf("J:%d\n",j);
for(int i=0; i<j; ++i)
{
//cout<<"1"<<endl;
cout<<a[i]<<" "<<fid(a[i])<<endl;
}
return 0;
}
H - 全文检索 HDU - 1277
瞎搞过的,极其难看,这里就不提了。
还有几个快乐的题比如说poj的1816,很有意思。
#include <iostream>
#include <cstdio>
#include <string>
#include <cstring>
using namespace std;
typedef long long ll;
const ll inf=0x3f3f3f3f;
const int maxn=2e6+5;
int tree[maxn][30],tot=0;
bool flag[maxn],vis[maxn];
int n,m,p[maxn];
int add(char *s) {
int root=0,id,len=strlen(s);
for(int i=0; i<len; ++i) {
if(s[i]=='*') id=26;
else if(s[i]=='?') id=27;
else id=s[i]-'a';
if(!tree[root][id]) tree[root][id]=++tot;
root=tree[root][id];
}
flag[root]=1;
return root;
}
void fid(char *s,int nroot,int pos) {
int root=nroot,id,len=strlen(s);
if(pos>len) return;
if(pos==len && flag[root]) {
vis[root]=1;
}
if(tree[root][26]) { //*
for(int j=pos; j<=len; ++j) {
fid(s,tree[root][26],j);
}
}
if(tree[root][27]) { //£¿
fid(s,tree[root][27],pos+1);
}
id=s[pos]-'a';
if(s[pos]>='a'&&s[pos]<='z'&&tree[root][id]) {
fid(s,tree[root][id],pos+1);
}
}
char tp[100];
int main()
{
int n,m;
scanf("%d%d",&n,&m);
for(int i=0;i<n;++i)
{
scanf("%s",tp);
p[i]=add(tp);
}
while(m--!=0)
{
scanf("%s",tp);
fid(tp,0,0);
int ok=0;
for(int i=0;i<n;++i)
{
if(vis[p[i]])
{
printf("%d ",i);
ok=1;
}
}
if(!ok) printf("Not match\n");
else
printf("\n");
for(int i=0;i<n;++i) vis[p[i]]=0;
}
return 0;
}
搞懂哈希之后会继续补的。