UVA 12526 Cellphone Typing (字典树)
Cellphone Typing
64-bit integer IO format: %lld Java class name: Main
However, this is not fast enough. The team is going to put together a dictionary of the common words that a user may type. The goal is to reduce the average number of keystrokes needed to type words that are in the dictionary. During the typing of a word, whenever the following letter is uniquely determined, the cellphone system will input it automatically, without the need for a keystroke. To be more precise, the behavior of the cellphone system will be determined by the following rules:
- The system never guesses the first letter of a word, so the first letter always has to be input manually by pressing the corresponding key.
- If a non-empty succession of letters c1c2...cn has been input, and there is a letter c such that every word in the dictionary which starts with c1c2...cn also starts with c1c2...cnc, then the system inputs c automatically, without the need of a keystroke. Otherwise, the system waits for the user.
For instance, if the dictionary is composed of the words `hello', `hell', `heaven' and `goodbye', and the user presses `h', the system will input `e' automatically, because every word which starts with `h' also starts with `he'. However, since there are words that start with `hel' and with `hea', the system now needs to wait for the user. If the user then presses `l', obtaining the partial word `hel', the system will input a second `l' automatically. When it has `hell' as input, the system cannot guess, because it is possible that the word is over, or it is also possible that the user may want to press `o' to get `hello'. In this fashion, to type the word `hello' the user needs three keystrokes, `hell' requires two, and `heaven' also requires two, because when the current input is `hea' the system can automatically input the remainder of the word by repeatedly applying the second rule. Similarly, the word `goodbye' needs just one keystroke, because after pressing the initial `g' the system will automatically fill in the entire word. In this example, the average number of keystrokes needed to type a word in the dictionary is then (3 + 2 + 2 + 1)/4 = 2.00.
Your task is, given a dictionary, to calculate the average number of keystrokes needed to type a word in the dictionary with the new cellphone system.
Input
Each test case is described using several lines. The first line contains an integer N representing the number of words in the dictionary ( 1![$ \le$](http://www.bnuoj.com/bnuoj/external/125/12526img1.png)
![$ \le$](http://www.bnuoj.com/bnuoj/external/125/12526img1.png)
Output
For each test case output a line with a rational number representing the average number of keystrokes needed to type a word in the dictionary. The result must be output as a rational number with exactly two digits after the decimal point, rounded if necessary.
Sample Input
4 hello hell heaven goodbye 3 hi he h 7 structure structures ride riders stress solstice ridiculous
Sample Output
2.00 1.67 2.71
题意:首先是一个n,表示后面有n行字符串。由小写字母组成
然后通过类似于现在的打字,要是遇到相同的字母,后面就马上连下去,比如第一个案例hello,hell,heaven,goodbye,
当输入h的时候,由于h开头的字符串有三个,而且第二个都是e,所以e会直接输出,而当再输入一个a的时候,heaven就直接出来了
而当输入的不是a,是l的时候,因为hello和hell从第三个开始的相同是hell所以,hell直接出来了。
而当第一个输入的是g的时候,因为g的字符串只有一个,所以直接出goodbye
所以hello要输入3次
hell要输入2次
heaven要输入2次
goodbye要输入1次
答案就是(3+2+2+1)/ 4
题解:用字典树来做,字典树是一种哈希树的变种,用来排序,储存,统计字符串,当然不单单是字符串
它是用结构体来储存的,然后通过结构体中的next[]来表示后面接着什么字符。
它是用公共前缀放在一起,这样就可以节省很多的空间。
组建树的时候通过将每个字符串末尾进行flag标记为-1(如hell和hello中,在hell后面的l这里,要标记-1),
而且将分叉点也标记为-1(如hello和heaven,中a这里要标记-1),然后当遍历的时候,每遇到一个flag为-1的时候就将那个字符串
所需输入的次数加1。
要注意,每次的第一个字母,都是要输入的。
后面当有分叉的时候,表明分叉的地方有分歧,输入时不会蹦出来,所以也要输入一次,如hello,heaven,但输入h的时候,只会蹦出he,后面的a要在输入
当有其它字符串当作前缀的时候,入hell,hello,要输入hello的时候,只会蹦出hell,后面还要自己输入一个o
也就只要关注这样三个地方就可以了,要是同时是末尾和分叉,只要加一次就够了,不要两次叠加
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <algorithm>
#include <iostream>
using namespace std;
#define maxn 200005
typedef struct Trie{ //字典树所用到的结构体
int flag; //用来标记这个结点是不是末尾,或者分叉点
Trie *next[26]; //用了next,就可以不用一个个记录下字符了,可以用第几个来表示,要是小写字母,就是26,要是加上大写字母,就是52,这样类推
}Trie;
char str[maxn][100]; //存放字符串,用于构造树,和查询遍历的时候用
Trie *root; //首个结点
void Insert(char *ch){
int len = strlen (ch), i, j, temp1 = 0, temp2 = 0;
Trie *p = root, *q;
for (i = 0; i < len; ++i){
int id = ch[i] - 'a'; //表示这个字符属于第几个位置,用位置来表示字符
if (p->next[id] == NULL){ //如果到这里开始,后面没有和这个字符串类似的前缀字符了,就开始分叉
q = (Trie *) malloc (sizeof (Trie)); //新构建一个结点,来存放开始的分叉
if (temp1 == 1 && temp2 == 0){ //这里是用来标记分叉时候的flag
p->flag = -1;
temp2 = 1;
}
q->flag = 1;
for (j = 0; j < 26; ++j) //新建结点要将next[]都赋值为NULL
q->next[j] = NULL;
p->next[id] = q;
p = q;
}
else {
temp1 = 1; //这里表示相同前缀
p = p->next[id];
}
}
p->flag = -1;
}
int Query(char *ch){ //用来记录每个字符串所需要输入几次
int ant = 1, len = strlen (ch), i;
Trie *p = root;
if (len == 1)
return 1;
for (i = 0; i < len; ++i){
int id = ch[i] - 'a';
if (p->next[id]->flag == -1 && i != len - 1) //这里还要注意,不要将自己的末尾当作标记来加1,所以要除去i ==len - 1时的情况
ant++;
p = p->next[id];
}
return ant;
}
int main (){
//freopen ("in.txt", "r", stdin);
int n, len, i;
while (scanf ("%d", &n) != EOF){
getchar();
root = (Trie *) malloc (sizeof (Trie)); //构建根节点
for (i = 0; i < 26; ++i)
root->next[i] = NULL;
for (i = 1; i <= n; ++i){
scanf ("%s", str[i]);
Insert(str[i]); //构造字典树
}
int sum = 0;
for (i = 1; i <= n; ++i) //遍历,确定每个字符串要输入几次
sum += Query(str[i]);
printf ("%.2lf\n", sum * 1.0 / n);
}
return 0;
}
/*
3
a
ab
abc
2.00
*/