题目链接:http://poj.org/problem?id=1451
http://acm.hdu.edu.cn/showproblem.php?pid=1298
题目大意:本题很有实际意义,模拟现实中的手机输入法,我最喜欢这种题目了,让我们在巩固算法的同时加强对现实事物的认知。言归正传,本题给定n个字符串和每个串出现的频率,每个字符串都由小写字母构成。再给定m个按键操作串,由数字1-9构成,1表示结束,其他每个按键都对应3-4个字母,和手机键盘一样。一个按键操作对应一个输出,输出的是频率最高的前缀,频率相同时输出字典序小的那个前缀。如果你还不是很明白,掏出你的手机按按看。
解题思路:这题卡了一天。第一天按照键盘的几个键来模拟,所以字典树中的next指针数组大小为10,表示0-9这几个儿子,这样查询的时候非常方便,只要进行线性转移就可以。比如说34,先从按键3的3个字符’d''e''f'找最大频率的那个串,到当前长度出现‘d''e''f'的串必定经过当前节点,然后到4的时候再做判断就可以。但很明显,这是错误的。因为通过记录最大频率的串的下标来输出前缀会出错,没办法正确输出频率最大的那个前缀。后来,经过CWW提醒,频率是要累加的,比如ha 3,hab 4,那么h的频率是7,a的频率也是7,b的频率则是4。这样只要随机输出到这个节点的字典序最小的字符串前缀就可以了。
但错,错,错,依然是错。我一直想不通,可能是查询的时候无法正确找到要输出的前缀吧。
所以,第二天果断换了思路,按照26个字母来模拟,这样建字典树的时候十分方便,套模版就可以。但是查询的时候就要搜索,因为按键操作是以数字的形式给出的,每个键对应3-4个字符,这样转移的时候就不止一种方法,必须每种都去尝试,然后输出频率最大并且字典序最小的前缀。我在膜拜了众人的解题报告之后,用优先队列
解决了这题。
不过,现在代码就比较难懂了。昨天的代码写得挺飘逸的。
测试数据:
5
3
ac 4
ab 3
bp 5
1
271
7
gewel 5
hello 3
hell 2
rubmarine 4
ruper 4
suring 3
suesday 5
6
439351
435561
7826274631
787371
7874641
78373291
3
abyb 3
abye 13
aaza 4
1
22931
5
hell 8
hello 4
idea 8
next 8
super 3
2
435561
43321
7
another 5
contest 6
follow 3
give 13
integer 6
new 14
program 4
6
1
77647261
6391
4681
26684371
77771
代码:
#include <stdio.h>
#include <queue>
#include <stdlib.h>
#include <string.h>
using namespace std;
#define MIN 120
#define MAX 1200
struct trienode {
int sum;
trienode *next[26];
}*root;
struct prionode{
int sum,slen;
char str[MIN];
trienode *trie;
friend bool operator < (prionode a,prionode b) {
//这样写可以避免用两个队列,长度短的先出队
return a.slen > b.slen || (a.slen == b.slen && //就可以根据长度判断该不该继续出队,不理解看Search()
(a.sum < b.sum || (a.sum == b.sum && strcmp(a.str,b.str) > 0)));
}
};
int n,m,p,num[MIN],keylow[MIN]; //每个按键有num[i]+1个字母,每个按键的第一个字母是keylow[i]+'a'
char dir[MAX][MIN],list[MIN]; //list表示按键操作串
priority_queue<prionode> qu;
void initial() {
for (int i = 2; i <= 9; ++i)
num[i] = 2;
num[7] = num[9] = 3;
keylow[2] = 0,keylow[3] = 3;
keylow[4] = 6,keylow[5] = 9;
keylow[6] = 12,keylow[7] = 15;
keylow[8] = 19,keylow[9] = 22;
}
trienode *CreateNode() {
trienode *p;
p = (trienode *) malloc (sizeof(trienode));
p->sum = 0;
for (int i = 0; i < 26; ++i)
p->next[i] = NULL;
return p;
}
void Release(trienode *p) {
for (int i = 0; i < 26; ++i)
if (p->next[i] != NULL) Release(p->next[i]);
free(p);
}
void Insert(char *dir,int pro) {
int i = 0,k,in;
trienode *p = root;
while (dir[i]) {
k = dir[i++] - 'a';
if (p->next[k] == NULL)
p->next[k] = CreateNode();
p->next[k]->sum += pro;
p = p->next[k];
}
}
void Search(char *list) {
int i = 0,k,in,j;
prionode now;
list[strlen(list)-1] = '\0'; //把最后的1去了
while (!qu.empty()) qu.pop(); //清空优先队列
now.slen = 0,now.str[0] = '\0';
now.trie = root,qu.push(now);
while (list[i]) {
in = list[i++] - '0';
while (!qu.empty()) {
//层次层次遍历
now = qu.top();
if (qu.top().slen == i) break;
else qu.pop();
int tp = keylow[in];
for (j = tp; j <= num[in] + tp; ++j)
if (now.trie->next[j] != NULL) {
prionode next;
next.trie = now.trie->next[j];
next.sum = next.trie->sum;
next.slen = i;
strcpy(next.str,now.str);
next.str[i-1] = j + 'a';
next.str[i] = '\0';
qu.push(next);
}
}
if (qu.empty()) printf("MANUALLY\n");
else printf("%s\n",qu.top().str);
}
printf("\n");
}
int main()
{
int i,j,k,t,cas;
initial();
scanf("%d",&t);
for (cas = 1; cas <= t; ++cas) {
root = CreateNode();
scanf("%d",&n);
for (i = 0; i < n; ++i)
scanf("%s%d",dir[i],&p),Insert(dir[i],p);
scanf("%d",&m);
printf("Scenario #%d:\n",cas);
for (i = 0; i < m; ++i)
scanf("%s",list),Search(list);
Release(root);
printf("\n");
}
}
本文ZeroClock原创,但可以转载,因为我们是兄弟。