一、编程题
题目描述
牛牛想尝试一些新的料理,每个料理需要一些不同的材料,问完成所有的料理需要准备多少种不同的材料。
输入描述:
每个输入包含 1 个测试用例。每个测试用例的第 i 行,表示完成第 i 件料理需要哪些材料,各个材料用空格隔开,输入只包含大写英文字母和空格,输入文件不超过 50 行,每一行不超过 50 个字符。
输出描述:
输出一行一个数字表示完成所有料理需要多少种不同的材料。
根据二中的思想 用字典序trie树方式 其中2100是一个安全上限 根据牛客网改的
如果这个上限超出内存限制 需要参考方法2变成动态分配
思路: 维护一个字典树序列 a[i][j]=k 编号i的第j个孩子的编号是K 编号是输入单词序列与字典树结合的顺序。
插入和查找同时进行时,考虑la和l都是新的,即后到的前缀作为单词也应计算 因此需要一个bool标记是否访问过
#include<iostream>
#include<cstdlib>
#include<string.h>
using namespace std;
char s[55];
int total = 0;
int cur = 0; //编号
struct tr{
int id;
bool endflag;
tr():id(0),endflag(false){}
};
tr trie[2500][26];
void count(int rt, char * s, int len)
{
for(int i=0;i<len;i++)
{
int gap = s[i]-'A';
if(!trie[rt][gap].id)//插入
{
trie[rt][gap].id = ++cur;
if(i == len-1) //结尾证明是新单词
{
total++;
trie[rt][gap].endflag = true;
//cout<<len-1<<' '<<total<<' '<<s<<endl;
}
}
if(i == len-1 && !trie[rt][gap].endflag) //结尾证明是新单词
{
total++;
trie[rt][gap].endflag = true;
// cout<<len-1<<' '<<total<<' '<<s<<endl;
}
rt = trie[rt][gap].id; //下一次的起始节点
}
}
int main()
{
int rt = 0;
//freopen("input.txt","r",stdin);
while(cin>>s)
{
count(rt,s,strlen(s));
//cout<<s<<endl;
}
//freopen("CON","r",stdin);
cout<<total<<endl;
//system("pause");
return 0;
}
方法2:
#include<iostream>
#include<cstring>
using namespace std;
int count = 0;//单词数统计,用于分配id,每个单词一个id
/*字典树节点类:
flag:词尾标志
id:节点hash值
next:后续字母节点连接地址
*/
class Trie_node{
public:
bool flag;
int id;
Trie_node *next[27];
Trie_node(){
flag = false;
id = 0;
memset(next, 0, sizeof(next));
}
}root;
/*hash函数:
返回字符串的hash值
*/
int Hash(char *s){
Trie_node *p = &root;
int len = 0;
while(s[len] != '\0'){
int index = s[len++] - 'A';
if(!p->next[index])
p->next[index] = new Trie_node;
p = p->next[index];
}
if(!p->flag){
p->id = count++;
p->flag = true;
}
return p->id;
}
int main(){
char step[51];
while(cin>>step){
Hash(step);
}
cout<<count<<endl;
return 0;
}
二、参考Trie树简介:
https://www.cnblogs.com/TheRoadToTheGold/p/6290732.html
1、单词是否出现
/*查询单词是否整体出现
trie tree的储存方式:将字母储存在边上,边的节点连接与它相连的字母
trie[rt][x]=tot:rt是上个节点编号,x是字母,tot是下个节点编号
*/
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#define maxn 2000010
using namespace std;
int tot=1,n;
int trie[maxn][26];
//bool isw[maxn];查询整个单词用
void insert(char *s,int rt)
{
for(int i=0;s[i];i++)
{
int x=s[i]-'a';
if(trie[rt][x]==0)//现在插入的字母在之前同一节点处未出现过
{
trie[rt][x]=tot++;//字母插入一个新的位置,否则不做处理
}
rt=trie[rt][x];//为下个字母的插入做准备
}
/*isw[rt]=true;标志该单词末位字母的尾结点,在查询整个单词时用到*/
}
bool find(char *s,int rt)
{
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 isw[rt]
}
char s[22];
int main()
{
tot=0;
int rt=1;
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
cin>>s;
insert(s,rt);
}
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
cin>>s;
if(find(s,rt))printf("YES\n");
else printf("NO\n");
}
return 0;
}
2、前缀出现次数
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
int trie[400001][26],len,root,tot,sum[400001];
bool p;
int n,m;
char s[11];
void insert()
{
len=strlen(s);
root=0;
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 search()
{
root=0;
len=strlen(s);
for(int i=0;i<len;i++)
{
int id=s[i]-'a';
if(!trie[root][id]) return 0;
root=trie[root][id];
}//root经过此循环后变成前缀最后一个字母所在位置的后一个位置
return sum[root];//因为前缀后移了一个保存,所以此时的sum[root]就是要求的前缀出现的次数
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
cin>>s;
insert();
}
scanf("%d",&m);
for(int i=1;i<=m;i++)
{
cin>s;
printf("%d\n",search());
}
}