题意:模拟手机九宫格输入法,输入w个字符串并给出每个字符串出现的次数,然后输入p组查询,每组的查询由一串数字组成,每输入一个数字输出到当前为止最有可能的字符串(如果不存在就输出MANUALLY),出现次数越多的字符串可能性越大。
题解:看题意肯定是要在字典树上操作的,但是与一般字典树不同的是这里多了一项出现次数。并且查询的时候一个数字可能同时代表几个字母,要找出其中出现次数最多的一个。所以首先在字典树建树时我们就要把每个单词中的每次字母出现次数都加上当前这个单词出现的次数。然后用dfs进行查找,我们遍历这个数字所代表的手机键包含的所有字符,如果在字典树中出现过就继续递归往下找,直到找到题目所要求的的长度,找到其中出现次数最大的一个字符,并输出根节点到这个字符的所有字符。
附上代码:
#include <stdio.h>
#include <string.h>
#include <iostream>
#include <algorithm>
using namespace std;
const int maxn=110;
char phone[][4] = {{'a','b','c'},{'d','e','f'},{'g','h','i'},{'j','k','l'},{'m','n','o'},{'p','q','r','s'},{'t','u','v'},{'w','x','y','z'}};
//phone表示手机上九个键每个键表示的字母
int num[8] = {3,3,3,3,3,4,3,4};//num表示每个数字上面包含几个字母
int w;//w保存每个字符在字典树每层出现的频率
char ans[maxn],s[maxn],str[maxn],a[maxn];//ans数组记录最终答案
struct node
{
int cnt;///cnt记录每个字母出现的频率
struct node *next[26];
node()
{
cnt = 0;
memset(next,0,sizeof(next));
}
};
node *root=NULL;
void buildtire(char *ss,int k)
{
node*q=root;
node *temp=NULL;
for(int i=0;i<strlen(ss);i++)
{
int v=ss[i]-'a';
if(q->next[v] == NULL)
{
temp = new node;
q->next[v] = temp;
q = q->next[v];
(q->cnt)+=k;///在出入时记录好每个字符出现的频率
}
else{
q = q->next[v];
(q->cnt)+=k;
}
}
}
void dfs(int st,int len,node *tr)
///dfs查找的思路是遍历这个数字包含的所有字符,如果在字典树中出现就继续递归往下找,
///直到找到题目所要求的长度,找到其中出现次数最大一个字符,并输出从根节点到这个字符所表示的字符串
{
if(st == len)///找到题目所要求长度
{
if(tr->cnt>w)///最后的答案是出现次数最大的
{
w = tr->cnt;
for(int i=0;i<len;i++)
ans[i] = s[i];
ans[len] = '\0';
}
return;
}
int L = str[st] - '2';///因为包含字母的数字是从2开始的,所以这里要减去‘2’
for(int i=0;i<num[L];i++)//枚举当前输入的手机键数字上包含几个字母
{
char c = phone[L][i];
if(tr->next[c-'a']!=NULL)
{ ///因为找到出现次数最大的字符时,它前边的字符必须可能在以前输入数字时有出现的可能,
///所以这里要控制某个数字包含的字母只有在字典树中的字符才往下递归
s[st] = c;
dfs(st+1,len,tr->next[c-'a']);
}
}
}
int main()
{
int T;
scanf("%d",&T);
int c=0;
while(T--)
{
root = new node;
int n,k;
scanf("%d",&n);
memset(a,0,sizeof(a));
for(int i=0;i<n;i++)
{
scanf("%s %d",a,&k);
buildtire(a,k);///字典树插入
}
int m;
scanf("%d",&m);
for(int i=0;i<m;i++){
scanf("%s",str);
if(i==0)
printf("Scenario #%d:\n",++c);
int len = strlen(str);
for(int j=1;j<len;j++){///因为最后一个1忽略不计,所以这里小于len就可以了
w = 0;
dfs(0,j,root);
if(w>0)
printf("%s\n",ans);
else
printf("MANUALLY\n");
}
printf("\n");
}
printf("\n");
}
return 0;
}
这题虽然常规方法可以用字典树+dfs做,但是看到还有人用map来做,果然有了STL就有了全世界。首先对于每个字母都有自己对应的数字,所以对于每个数字序列都有它对应的字符串,那就可以用一个map来离线保存每个数字序列最有可能出现的字符串。而对于每个数字序列最有可能对应的字符串,可以在一开始输入单词时就对每个单词进行映射把它变成数字序列,虽然数字对应单词不是唯一的,但是单词对应数字是唯一的,然后在不断插入单词的过程中,对应每一个前缀都更新一下这个前缀对应的数字序列,最后查询的时候直接查询输出。(给出这个做法的原博客链接:点击打开链接)
#include <stdio.h>
#include <string.h>
#include <iostream>
#include <algorithm>
#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;
}