字典树模板
//对于字符串比较多的要统计个数的,map被卡的情况下,直接用字典树
//很多题都是要用到节点下标来表示某个字符串
const int maxn =2e6+5;//如果是64MB可以开到2e6+5,尽量开大
int tree[maxn][30];//tree[i][j]表示节点i的第j个儿子的节点编号
bool flagg[maxn];//表示以该节点结尾是一个单词
int tot;//总节点数
void insert_(char *str)
{
int len=strlen(str);
int root=0;
for(int i=0;i<len;i++)
{
int id=str[i]-'0';
if(!tree[root][id]) tree[root][id]=++tot;
root=tree[root][id];
}
flagg[root]=true;
}
bool find_(char *str)//查询操作,按具体要求改动
{
int len=strlen(str);
int root=0;
for(int i=0;i<len;i++)
{
int id=str[i]-'0';
if(!tree[root][id]) return false;
root=tree[root][id];
}
return true;
}
void init()//最后清空,节省时间
{
for(int i=0;i<=tot;i++)
{
flagg[i]=false;
for(int j=0;j<10;j++)
tree[i][j]=0;
}
tot=0;//RE有可能是这里的问题
}
字典树第一题
HDU1251
题意就是统计出以某个字符串为前缀的单词数量,首先构建出trie树并记录每个节点的访问次数,然后在上面查询就好了,模板题。
HDU1251代码
#include<stdio.h>
#include<iostream>
#include<string.h>
using namespace std;
const int maxn =2e6+5;
int tree[maxn][30];
int sum[maxn];
int tot;
void insert_(char *str)
{
int len=strlen(str);
int root=0;
for(int i=0;i<len;i++)
{
int id=str[i]-'a';
if(!tree[root][id]) tree[root][id]=++tot;
sum[tree[root][id]]++;//记录节点访问次数
root=tree[root][id];
}
//root在此对应某个单词,一一对应
}
int find_(char *str)
{
int len=strlen(str);
int root=0;
for(int i=0;i<len;i++)
{
int id=str[i]-'a';
if(!tree[root][id]) return 0;
root=tree[root][id];
}
return sum[root];//返回当前字符串结尾节点的访问次数,也就是作为前缀的出现次数
}
char ss[maxn];
int main()
{
tot=0;
while(gets(ss))
{
if(ss[0]=='\0') break;
insert_(ss);
}
while(scanf("%s",ss)!=EOF)
{
printf("%d\n",find_(ss));
}
return 0;
}
字典树第二题
HDU2072
题意就是出现的不同单词个数
直接把字符全部插入Trie树中,然后统计所有具有flagg标记的节点个数就好了。
也可以边插入边统计,如果当前字符串结尾下标已经被标记,就不对答案做贡献,否则ans++.
第二题代码
#include<stdio.h>
#include<iostream>
#include<string.h>
#include<sstream>
using namespace std;
const int maxn =2e6+5;
int tree[maxn][30];
bool flagg[maxn];
int tot;
void insert_(string str)
{
int len=str.size();
int root=0;
for(int i=0;i<len;i++)
{
int id=str[i]-'a';
if(!tree[root][id]) tree[root][id]=++tot;
root=tree[root][id];
}
flagg[root]=true;
}
bool find_(string str)
{
int len=str.size();
int root=0;
for(int i=0;i<len;i++)
{
int id=str[i]-'a';
if(!tree[root][id]) return true;//该单词没有出现过
root=tree[root][id];
}
if(flagg[root]) return false;//出现过,不再算贡献
else return true;
}
string str1,str2;
int main()
{
ios::sync_with_stdio(false);
while(getline(cin,str1))
{
if(str1=="#") break;
int ans=0;
stringstream ss(str1);
while(ss>>str2)
{
if(find_(str2))
{
ans++;
insert_(str2);
}
}
cout<<ans<<endl;
for(int i=0;i<tot;i++)
{
flagg[i]=false;
for(int j=0;j<30;j++)
tree[i][j]=0;
}
tot=0;
}
return 0;
}
字典树第三题
POJ2001
题意就是求一个能代表这个字符串的最短前缀,也就是只有这个字符串具有的前缀。
做法很显然,我们先构建好Trie树,然后对每个单词进行find,递归到直到节点出现次数为1,表示这个节点只有这一个单词走过,返回就ok。这里我用了string不断拼接字符,然后直接返回,减少了一些代码量。
POJ2001代码
#include<stdio.h>
#include<iostream>
#include<string.h>
using namespace std;
const int maxn =2e6+5;
int tree[maxn][30];
int sum[maxn];
int tot;
void insert_(char *str)
{
int len=strlen(str);
int root=0;
for(int i=0;i<len;i++)
{
int id=str[i]-'a';
if(!tree[root][id]) tree[root][id]=++tot;
sum[tree[root][id]]++;
root=tree[root][id];
}
}
string find_(char *str)
{
int len=strlen(str);
int root=0;
string ans="";
for(int i=0;i<len;i++)
{
int id=str[i]-'a';
root=tree[root][id];
ans+=str[i];
if(sum[root]==1) return ans;
}
}
char ss[1005][25];
int main()
{
int tot=0;
while(scanf("%s",ss[tot++])!=EOF)
{
insert_(ss[tot-1]);
}
for(int i=0;i<tot;i++)
{
printf("%s %s\n",ss[i],find_(ss[i]).c_str());
}
return 0;
}
字典树第四题
POJ3630
本题题意就是给你一个字符串集合,问你否所有的字符串都不是其他人的前缀。
首先我们构造出Trie树,然后对每个字符串find,当find的路径上如果出现其他字符串结尾标记,就说明其他字符串是当前字符串的前缀。注意这里对每个字符串find的时候只要搜索到 l e n − 1 len-1 len−1即可,如果搜索到 l e n len len,那么将会将本身的字符串统计进去。
POJ3630代码
#include<stdio.h>
#include<iostream>
#include<string.h>
using namespace std;
const int maxn =2e6+5;
int tree[maxn][15];
bool flagg[maxn];
int tot;
void insert_(char *str)
{
int len=strlen(str);
int root=0;
for(int i=0;i<len;i++)
{
int id=str[i]-'0';
if(!tree[root][id]) tree[root][id]=++tot;
root=tree[root][id];
}
flagg[root]=true;
}
int find_(char *str)
{
int len=strlen(str);
int root=0;
for(int i=0;i<len-1;i++)
{
int id=str[i]-'0';
root=tree[root][id];
if(flagg[root]) return true;//路径上出现过其他字符串的结尾标记
}
return false;
}
char ss[10005][12];
int main()
{
int n,t;
scanf("%d",&t);
while(t--)
{
scanf("%d",&n);
for(int i=0;i<n;i++)
{
scanf("%s",ss[i]);
insert_(ss[i]);
}
int flag=0;
for(int i=0;i<n;i++)
{
if(find_(ss[i]))
{
flag=1;
break;
}
}
if(flag==0)
printf("YES\n");
else
printf("NO\n");
for(int i=0;i<=tot;i++)
{
flagg[i]=false;
for(int j=0;j<10;j++)
tree[i][j]=0;
}
tot=0;
}
return 0;
}
字典树第五题
LightOJ1224
本题题意是让你找出一个字符串,使该字符串作为前缀的次数 ∗ * ∗该字符串的长度结果最大
我们首先构建好trie树,我们利用记录节点出现次数的方式存储,这时候结果就是 s u m [ r o o t ] ∗ 当 前 的 l e n sum[root]*当前的len sum[root]∗当前的len,对于当前的len也就是递归深度,如果我们要存入所有节点之后再重新扫一遍Trie树复杂度会高很多,所以我们可以在插入字符串的时候进行统计,用一个maxx保存 s u m [ r o o t ] ∗ 当 前 l e n sum[root]*当前len sum[root]∗当前len的最大值就好了.具体实现看代码。
LightOJ1224代码
#include<stdio.h>
#include<iostream>
#include<string.h>
#include<map>
using namespace std;
const int maxn =2e6+5;
int tree[maxn][5];//只有四种字符
int sum[maxn];
int tot;
int ans;
map<char,int> mm;//字符映射
void insert_(char *str)
{
int len=strlen(str);
int root=0;
for(int i=0;i<len;i++)
{
int id=mm[str[i]];
if(!tree[root][id]) tree[root][id]=++tot;
sum[tree[root][id]]++;
ans=max(ans,sum[tree[root][id]]*(i+1));//插入时直接统计结果
root=tree[root][id];
}
}
char ss[55];
int main()
{
mm['A']=0;
mm['C']=1;
mm['G']=2;
mm['T']=3;
int t,n;
scanf("%d",&t);
int cnt=1;
while(t--)
{
ans=0;
scanf("%d",&n);
while(n--)
{
scanf("%s",ss);
insert_(ss);
}
for(int i=0;i<=tot;i++)
{
sum[i]=0;
for(int j=0;j<4;j++)
tree[i][j]=0;
}
tot=0;
printf("Case %d: %d\n",cnt++,ans);
}
return 0;
}
字典树第六题
POJ2513
本题题意是给一堆木棒,每种木棒左右两端有两种颜色,木棒进行拼接的时候,只有相同颜色之间才可以拼接,问最后是否可以将所有木棒拼为一根木棒。
我们考虑把同一种颜色的点聚在一起,我们就可以得到一个无向图,如果这个无向图是欧拉图,代表展开之后可以一笔走完,也就是可以连接成一条木棒。所以我们用trie树判断每种颜色出现的次数,再用并查集判一下图是否连通,最后用欧拉图的性质判断一下是否为欧拉图就好了(只存在两个或者0个奇度的点)
POJ2513代码
#include<stdio.h>
#include<iostream>
#include<string.h>
#include<sstream>
using namespace std;
const int maxn =2e6+5;
int tree[maxn][30];
int sum[maxn];
int rank_[maxn];
int f[maxn];
int vis[maxn];
int flag,flag2;
int tot;
int cnt;
int pos1,pos2;
int insert_(char *str)
{
int len=strlen(str);
int root=0;
for(int i=0;i<len;i++)
{
int id=str[i]-'a';
if(!tree[root][id]) tree[root][id]=++tot;
root=tree[root][id];
}
if(!vis[root]) vis[root]=++cnt;
if(f[cnt]==0) f[cnt]=cnt;//在这里重置f数组,节省一部分时间
return vis[root];
}
int find_(int x){ return f[x]==x?x:f[x]=find_(f[x]);}
void union_(int x,int y)
{
x=find_(x),y=find_(y);
if(x!=y)
{
if(rank_[x]>rank_[y])
{
f[y]=x;
}
else
{
f[x]=y;
if(rank_[x]==rank_[y]) rank_[x]++;
}
}
}
char str1[12],str2[12];
int main()
{
while(scanf("%s%s",str1,str2)!=EOF)
{
pos1=insert_(str1);//统计该颜色对应的下标
sum[pos1]++;
pos2=insert_(str2);
sum[pos2]++;//统计每种颜色的度
union_(pos1,pos2);
}
for(int i=1;i<=cnt;i++)
{
if(sum[i]%2==1)
{
flag++;
}
if(find_(i)!=find_(1))
{
printf("Impossible\n");//非连通图
return 0;
}
}
if(flag==0||flag==2) printf("Possible\n");//欧拉图判定
else printf("Impossible\n");
return 0;
}
字典树第七题
POJ1451
本题题意比较有意思,大概就是模拟手机输入法,先给你一个用户的词库,即每个单词出现的次数,这个时候再按照九键输入法给你一个数字序列,问你在输入这个序列的过程中,出现的字符串顺序,也就是对于每个数字序列,给出一个最有可能出现的字符串。
这道题我的做法比较巧妙,但是不怎么会算复杂度,还是很快的过去了。首先我们考虑,对于每个数字序列,我们都可以用一个string去映射,这样我们可以用一个map来离线保存每个数字序列对应的最有可能出现的字符串,可是我们怎么知道每个数字序列最有可能出现的字符串是哪个呢,首先我们对单词进行映射,把每个单词映射成数字序列,然后在插入的过程中,对于每一个前缀都更新一下这个前缀对应的map,最后查询的时候就可以直接输出了。
POJ1451代码
#include<stdio.h>
#include<iostream>
#include<string.h>
#include<sstream>
#include<map>
using namespace std;
const int maxn =2e6+5;
int tree[maxn][30];
int sum[maxn];
int tot;
int belong[30];
map<string,int> mm;//每种数字序列最大出现次数
map<string,string>mm2;//每种序列对应的最大次数的字符串
void insert_(char *str,int num)
{
int len=strlen(str);
int root=0;
string strr="";
string str2="";
for(int i=0;i<len;i++)
{
int id=str[i]-'a';
strr+=(belong[id]+'0');//将插入的字符串映射成数字序列
str2+=str[i];//当前前缀
if(!tree[root][id]) tree[root][id]=++tot;
root=tree[root][id];
sum[root]+=num;//统计每个前缀出现次数
if(mm[strr]<sum[root])//更新map
{
mm[strr]=sum[root];
mm2[strr]=str2;
}
}
return ;
}
char ss[maxn];
void init()//数字与字母间的映射
{
for(int i=0;i<25;i++)
{
if(i<18)
belong[i]=2+i/3;
else
belong[i]=2+(i-1)/3;
}
belong[25]=9;
return ;
}
int main()
{
init();
int cnt=1;
int n,m,t,num;
scanf("%d",&t);
while(t--)
{
mm.clear();
mm2.clear();
scanf("%d",&n);
while(n--)
{
scanf("%s%d",ss,&num);
insert_(ss,num);
}
scanf("%d",&m);
printf("Scenario #%d:\n",cnt++);
while(m--)
{
scanf("%s",ss);
string tmp="";
int len=strlen(ss);
for(int i=0;i<len-1;i++)
{
tmp+=ss[i];//对于每个前缀直接输出
if(!mm2.count(tmp)) printf("MANUALLY\n");
else printf("%s\n",mm2[tmp].c_str());
}
printf("\n");
}
for(int i=0;i<tot;i++)
{
sum[i]=0;
for(int j=0;j<30;j++)
tree[i][j]=0;
}
printf("\n");
}
return 0;
}
字典树第八题
POJ1816
本题的题意是给你n个模式串和m个匹配串,模式串中有 ? 和 ∗ ?和* ?和∗两种字符,?可以匹配任意一种字符, ∗ * ∗可以匹配任意个字符,问每种匹配串可以和之前哪些模式串匹配。
我们先构建好字典树,对于每个匹配串实现find,find的时候用类似dfs的写法,如果当前节点的字符或者?存在,直接dfs下一个位置的字符,如果当前字符对应的位置有 ∗ * ∗存在,那么就从匹配串之后的每一个位置进行往下dfs,因为可以匹配任意个字符。而dfs计数的条件是匹配串匹配结束而且达到模式串某个结尾标记。则在这个模式串上做标记。需要注意的细节是此时不能直接return,因为有可能此时的字符串和剩下的匹配串还能匹配,类似模式串A : AA** 模式串B: AA*** 匹配串为AAA ,那么匹配到A字符串之后则不能停止,要继续往下搜索。
POJ1816代码
#include<stdio.h>
#include<iostream>
#include<string.h>
#include<sstream>
using namespace std;
const int maxn =2e6+5;
int tree[maxn][30];
bool flag[maxn];
bool vis[maxn];
int tot;
int insert_(char *str)
{
int len=strlen(str);
int root=0;
for(int i=0;i<len;i++)
{
int id;
if(str[i]=='*') id=26;//*号存在第26位
else if(str[i]=='?') id=27;//?存在第27位
else id=str[i]-'a';
if(!tree[root][id]) tree[root][id]=++tot;
root=tree[root][id];
}
flag[root]=true;//结尾符标记
return root;
}
void find_(char *str,int pos1,int pos2)//pos1 模式串下标 pos2 匹配串下标
{
int len=strlen(str);
if(pos2==len&&flag[pos1])//达到条件则对该字符串进行标记
{
vis[pos1]=1;//对该单词的下标进行标记
}
int root=pos1;
int id=str[pos2]-'a';
if(tree[root][id])//存在相同字符
{
int tmp=tree[root][id];
find_(str,tmp,pos2+1);
}
if(tree[root][27])//存在?
{
int tmp=tree[root][27];
find_(str,tmp,pos2+1);
}
if(tree[root][26])//存在*
{
int tmp=tree[root][26];
for(int j=pos2;j<=len;j++)//对之后的每一个位置进行dfs,要注意从pos2开始,因为*号可以不匹配
find_(str,tmp,j);
}
return ;
}
char ss[maxn];
int pos[maxn];
int main()
{
int n,m;
scanf("%d%d",&n,&m);
for(int i=0;i<n;i++)
{
scanf("%s",ss);
pos[i]=insert_(ss);//插入时顺带获取下标
}
while(m--)
{
int flag=0;
scanf("%s",ss);
find_(ss,0,0);
for(int i=0;i<n;i++)
{
if(vis[pos[i]])
{
flag=1;
printf("%d ",i);
}
}
for(int i=0;i<n;i++)
{
if(vis[pos[i]])
{
vis[pos[i]]=0;
}
}
if(flag==0) printf("Not match");
printf("\n");
}
return 0;
}
字典树第九题
HDU1247
本题题意是问你某个单词是否可以拆成单词表中的其他两个单词。
我们可以建两颗Trie树,然后分别正序倒序插入每个单词,对每个单词查询的时候,我们分别正序倒序查询,对出现过单词的前缀下标进行标记,对每个出现过单词的后缀进行标记,最后扫描标记数组,如果某个位置前缀后缀均被标记过,则表示可以拆成单词表中的两个其他单词。
HDU1247代码
#include<stdio.h>
#include<iostream>
#include<string.h>
#include<sstream>
#include<set>
using namespace std;
const int maxn =2e6+5;
int tree[maxn][30],tree2[maxn][30];
set<string> s;
bool flagg[maxn],flagg2[maxn];
int sum[55];
int tot,tot2;
void insert_(char *str)
{
int len=strlen(str);
int root=0;
for(int i=0;i<len;i++)
{
int id=str[i]-'a';
if(!tree[root][id]) tree[root][id]=++tot;
root=tree[root][id];
}
flagg[root]=true;
return ;
}
void find_(char *str)
{
int len=strlen(str);
int root=0;
for(int i=0;i<len-1;i++)
{
int id=str[i]-'a';
root=tree[root][id];
if(flagg[root]) sum[i]++;//当前前缀是其他单词
}
return ;
}
void insert2_(char *str)
{
int len=strlen(str);
int root=0;
for(int i=0;i<len;i++)
{
int id=str[i]-'a';
if(!tree2[root][id]) tree2[root][id]=++tot2;
root=tree2[root][id];
}
flagg2[root]=true;
return ;
}
void find2_(char *str)
{
int len=strlen(str);
int root=0;
for(int i=0;i<len-1;i++)
{
int id=str[i]-'a';
root=tree2[root][id];
if(flagg2[root])
{
sum[len-i-2]++;//当前前缀是其他单词,对反转后的位置进行标记
}
}
return ;
}
char ss[50005][55];
int main()
{
int cnt=0;
while(scanf("%s",ss[cnt++])!=EOF)
{
insert_(ss[cnt-1]);//正序插入到第一棵树
strrev(ss[cnt-1]);
insert2_(ss[cnt-1]);//倒叙插入第二颗树
strrev(ss[cnt-1]);
}
for(int i=0;i<cnt;i++)
{
int len=strlen(ss[i]);
for(int j=0;j<len;j++)
{
sum[j]=0;
}
find_(ss[i]);//在第一棵树上查询并标记
strrev(ss[i]);
find2_(ss[i]);//在第二颗树上查询并标记
strrev(ss[i]);
for(int j=0;j<len;j++)
{
if(sum[j]==2)//两次查询均标记过的位置
{
string tmp=ss[i];
s.insert(tmp);
break;
}
}
}
set<string>::iterator it;
for(it=s.begin();it!=s.end();++it)
{
printf("%s\n",(*it).c_str());
}
return 0;
}
字典树第十题
POJ2408
本题题意是,把可以通过重新排列变成相同单词的单词放入一个集合,最后按照集合元素由多到少输出前五个集合,如果集合元素相同,按照字典序由小到大输出
我们考虑,如果两个单词可以通过重新排列组合变成相同单词,那么他们的字典序最小的排列方式一定是相同的,所以我们可以利用每个元素的最小排列方式判定是否在同一个集合,字典树在这里用于判定某个字符串时候出现过。最后用set来保存以便维持字典序
POJ2408代码
#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<string.h>
#include<set>
#include<vector>
using namespace std;
const int maxn = 1e6+5;
int tree[maxn][30];
char ss[maxn];
int tot,anstot;
int num[30];
int vis[maxn];
struct data
{
int num;
set<string> s;
}cnt[maxn];
bool cmp(const data &a,const data &b)
{
if(a.num==b.num)
{
return *(a.s.begin())<*(b.s.begin());
}
return a.num>b.num;
}
void insert_(char *str)
{
int root=0;
int len= strlen(str);
string str2="";
for(int i=0;i<len;i++)
{
num[str[i]-'a']++;
}
for(int i=0;i<26;i++)//在此转换为字典序最小的字符串
{
while(num[i]!=0)
{
str2+=(i+'a');
num[i]--;
}
}
for(int i=0;i<len;i++)
{
int id=str2[i]-'a';
if(!tree[root][id]) tree[root][id]=++tot;
root=tree[root][id];
}
if(!vis[root]) vis[root]=++anstot;//若未出现过,赋予编号
cnt[vis[root]].num++;//将此字符串所在的结构体num变量+1
string tmp=str;
cnt[vis[root]].s.insert(tmp);
return ;
}
int main()
{
while(scanf("%s",ss)!=EOF)
{
insert_(ss);
}
sort(cnt+1,cnt+1+anstot,cmp);
for(int i=1;i<=5;i++)
{
printf("Group of size %d: ",cnt[i].num);
set<string>::iterator it;
for(it=cnt[i].s.begin();it!=cnt[i].s.end();++it)
{
printf("%s ",(*it).c_str());
}
printf(".\n");
}
return 0;
}
字典树第十一题
HDU1075
本题的题意是给你火星文与地球文的映射方式,然后给你一个火星文组成的文本,若某单词在映射文本中出现过,则输出映射之后的文本。否则输出原文本。
我们可以建立trie树,插入火星文本,将返回的下标pos与地球文相对应,在翻译文本的时候,从前往后截取每一段单词,在trie树上查找该单词是否出过,要注意必须是单词,而不能是某单词的前缀。若找到则返回下标,然后输出该下标对应的地球文,否则返回-1,输出原文本。
HDU1075代码
#include<stdio.h>
#include<algorithm>
#include<string.h>
#include<iostream>
using namespace std;
const int maxn = 2e6+5;
int tree[maxn][30];
int flagg[maxn];
int pos[maxn];
int tot,tot2;
int insert_(char *str)
{
int len=strlen(str);
int root=0;
for(int i=0;i<len;i++)
{
int id=str[i]-'a';
if(!tree[root][id]) tree[root][id]=++tot;
root=tree[root][id];
}
flagg[root]=true;
return root;//返回下标,对应的是一个单词
}
int find_(char *str)
{
int root=0;
int len=strlen(str);
for(int i=0;i<len;i++)
{
int id=str[i]-'a';
if(!tree[root][id]) return -1;
root=tree[root][id];
}
if(flagg[root]) return root;//若为完整相匹配单词,返回下标
else return -1;
}
char ss1[maxn][12];
char tmp[maxn];
int main()
{
int cnt=0;
scanf("%s",tmp);
while(scanf("%s",ss1[cnt])!=EOF)
{
if(ss1[cnt][0]=='E')
{
scanf("%s",tmp);
break;
}
scanf("%s",tmp);
pos[insert_(tmp)]=cnt;//将返回的下标与地球文进行匹配
cnt++;
}
getchar();
while(gets(tmp)!=NULL)
{
if(tmp[0]=='E') break;
int len=strlen(tmp);
char str[12];
int cnt;
for(int i=0;i<len;i++)
{
if(tmp[i]>='a'&&tmp[i]<='z'&&i<len)
{
cnt=0;
while(tmp[i]>='a'&&tmp[i]<='z')
{
str[cnt++]=tmp[i++];
}
str[cnt]='\0';//截取单词,存在str中
int pp=find_(str);
if(pp!=-1)
printf("%s",ss1[pos[find_(str)]]);//输出对应的地球文
else
printf("%s",str);
printf("%c",tmp[i]);
}
else
printf("%c",tmp[i]);
}
printf("\n");
}
return 0;
}
字典树第十二题
Lightoj1269
本题的题意是求出一段连续的区间,使这段区间异或和最小/最大。分别输出最大异或值和最小异或值。
区间异或和的题目一般都要用到前缀异或和,我们这里可以预处理出前缀异或和,然后我们考虑,对于每个值,在字典树上查找之前出现过的最优的匹配,利用字典树的特点,将其拆为二进制,贪心的去查找,查找最大值则是0找1,1找0,查找最小值则是1找1,0找0。维护一下最大值与最小值即可。Find的时候可以利用两个不同的root并行查找最大值与最小值,减少代码量。
Lightoj1269代码
#include<stdio.h>
#include<iostream>
#include<string.h>
using namespace std;
const int maxn =3e6+5;
int tree[maxn][2];
int tot;
int ans,ans2;
int flagg[maxn];
void insert_(int num)
{
int root=0;
int pos=30;
int id;
while(pos!=-1)
{
if(num&(1<<pos)) id=1;
else id=0;
if(!tree[root][id]) tree[root][id]=++tot;
root=tree[root][id];
pos--;
}
return ;
}
void find_(int num)
{
int root=0;
int root2=0;
int pos=30;
int id;
ans=0;
ans2=0;
while(pos!=-1)
{
if(num&(1<<pos)) id=1;
else id=0;
if(tree[root][id^1])//最大值优先找相反的0-1,1-0
{
ans^=(1<<pos);
root=tree[root][id^1];
}
else
{
root=tree[root][id];
}
if(tree[root2][id])//最小值优先找相同的0-0,1-1
{
root2=tree[root2][id];
}
else
{
ans2^=(1<<pos);
root2=tree[root2][id^1];
}
pos--;
}
}
int a[maxn];
int sum[maxn];
int main()
{
int n,t;
int cnt=1;
scanf("%d",&t);
while(t--)
{
tot=0;
int maxx=0;
int minn=0x3f3f3f3f;
scanf("%d",&n);
insert_(0);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
sum[i]=a[i]^sum[i-1];
find_(sum[i]);//先查找
minn=min(ans2,minn);
maxx=max(ans,maxx);
insert_(sum[i]);//后插入,避免本身造成影响
}
printf("Case %d: %d %d\n",cnt++,maxx,minn);
for(int i=0;i<=tot;i++)
{
tree[i][0]=0;
tree[i][1]=0;
}
}
return 0;
}
字典树第十三题
UVA-10887
本题题意就是给你AB两个字符串集,问你以A中字符串作为前缀,B中字符串作为后缀,可以得到多少个不同的字符串,就对每个拼接后的字符串放进字典树,统计单词个数就可以了
#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<string.h>
using namespace std;
const int maxn = 2e6+5;
int root,cnt;
int ans;
int tree[maxn][26];
int end_[maxn];
int newnode()
{
end_[cnt]=0;
for(int i=0;i<26;i++)
tree[cnt][i]=-1;
return cnt++;
}
void init()
{
ans=0;
cnt=0;
root=newnode();
}
void insert_(string str)
{
int tmp=root;
int len=str.size();
for(int i=0;i<len;i++)
{
int id=str[i]-'a';
if(tree[tmp][id]==-1)
{
tree[tmp][id]=newnode();
}
tmp=tree[tmp][id];
}
if(end_[tmp]==0)
{
end_[tmp]=1;
ans++;
}
return ;
}
string str1[1505];
string str2[1505];
string str3;
char tmp1[15];
char tmp2[15];
int main()
{
int n,m,t;
int cnt=1;
scanf("%d",&t);
while(t--)
{
init();
scanf("%d%d",&n,&m);
getchar();
for(int i=0;i<n;i++)
{
gets(tmp1);//读入可能有空行,用gets
str1[i]=tmp1;
}
for(int i=0;i<m;i++)
{
gets(tmp2);
str2[i]=tmp2;
}
for(int i=0;i<n;i++)
{
for(int j=0;j<m;j++)
{
str3=str1[i]+str2[j];//拼接
insert_(str3);
}
}
printf("Case %d: %d\n",cnt++,ans);
}
return 0;
}
未完待续…