題意:模擬手機九宮格輸入法,輸入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;
}