poj 1451 T9 字典树

题目链接:poj.org/problem?id=1451

题目背景:为了方便手机用户发短信,希望在用户按键时,根据每个词出现的频数,给出每个阶段最有可能要打的词。

PS:相同的前缀频数要相加计算。

题目大意:有不同的几组测试样例,每个测试样例中有w个词及每个词出现的频数,然后是p组要打的词,按1键表示结束输入。要求输出按到每个键时最可能要打的词。


解题思路:

根据每个单词建立一棵“单词字典树”(A树), 附加域记录每个字母出现的频数。再根据“单词字典树”建立一棵“数字字典树”(B树),附加域里记录最大频数及对应的单词下标。

下面我们细细地研究下:首先说,按每个词去建字典树应该都会。现在呢,要在建A树的同时建起B树来。所以我们需要一个映射,一个从字母到数字键盘的映射,根据这个映射关系去更新B树。比如,在“hell”和“hello”之后出现了“idea”,此时,在A树中只是新加进来个词而已,而在B树中则不然,因为idea对应的数字键为4332,和前面的hell的前两位43是相同的,因此,在B树中更新时,4和4之后3的频数值将从hell和hello的7改为idea的8,下标值也将从hello的下标2改为idea的下标3。如图:


这样,当按键时,就可以直接从B树中读取出下标来进行输出了。


OK,具体代码如下(用时47MS):

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define maxn 0x0400

typedef struct word {
	int p;	// probability value
	struct word* next[26];
}word, *W;

typedef struct dig {
	int p;	// probability value
	int w;	// word's sub in dict[]
	struct dig* next[10];
}dig, *D;

// From alpha to digit
int atod[26] = {2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5,
	5, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 9, 9, 9, 9};
char dict[maxn][0x80];
W wroot = NULL;
D droot = NULL;

inline W getW() {
	int i;
	W p = (W)malloc(sizeof(word));
	p->p = 0;
	for (i = 0; i < 26; i++) {
		p->next[i] = NULL;
	}
	return p;
}
inline D getD() {
	int i;
	D p = (D)malloc(sizeof(dig));
	p->p = 0;
	for (i = 0; i < 10; i++) {
		p->next[i] = NULL;
	}
	return p;
}

void delw(W p) {
	int i;
	for (i = 0; i < 26; i++) {
		if (p->next[i]) {
			delw(p->next[i]);
		}
	}
	free(p);
}

void deld(D p) {
	int i;
	for (i = 0; i < 10; i++) {
		if (p->next[i]) {
			deld(p->next[i]);
		}
	}
	free(p);
}

int main() {
	int cs, x, n, i, p, j, q;
	char s[0x80];
	scanf("%d", &cs);
	for (x = 1; x <= cs; x++) {
		W wp = wroot = getW();
		D dp = droot = getD();
		scanf("%d", &n);
		for (i = 1; i <= n; i++) {
			scanf("%s %d", s, &p);
			strcpy(dict[i], s); // 存下所有的词
			wp = wroot;
			dp = droot;
			for (j = 0; s[j]; j++) {
				int pos = s[j] - 97;
				if (wp->next[pos] == NULL) {
					wp->next[pos] = getW();
				}
				if (dp->next[atod[pos]] == NULL) {
					dp->next[atod[pos]] = getD();
				}
				wp = wp->next[pos];
				wp->p += p;
				dp = dp->next[atod[pos]];
				// 下面是根据A树建B树的过程
				if (wp->p > dp->p) {
					dp->p = wp->p; // 更新probability值
					dp->w = i; // 更新对应单词的下标
				}
			}
		}
		scanf("%d", &q);
		printf("Scenario #%d:\n", x);
		while (q--) {
			scanf("%s", s);
			dp = droot;
			for (i = 0; s[i] != 49; i++) {
				int pos = s[i] - 48;
				if (dp) { // 如果有这种按键方式的词
					dp = dp->next[pos];
					if (dp) {
						// 可能按到上一个键时还有
						// 到这个键时就没有了
						for (j = 0; j <= i; j++) {
							putchar(dict[dp->w][j]);
						}
					} else {
						printf("MANUALLY");
					}
				} else {
					printf("MANUALLY");
				}
				printf("\n");
			}
			printf("\n");
		}
		printf("\n");
		delw(wroot);
		deld(droot);
	}
	return 0;
}


在此呢,可以单独写个空间申请以加快速度(连free也省了,用时16MS):

word WS[100000];
dig DS[100000];
inline void init() {
	WTOP = DTOP = 0;
}

inline W getW() {
	int i;
	W p = &WS[WTOP++];
	p->p = 0;
	for (i = 0; i < 26; i++) {
		p->next[i] = NULL;
	}
	return p;
}
inline D getD() {
	int i;
	D p = &DS[DTOP++];
	p->p = 0;
	for (i = 0; i < 10; i++) {
		p->next[i] = NULL;
	}
	return p;
}

正所谓,要哈希呢,就彻头彻尾的哈希吧(用时0MS):


#include <stdio.h>

char *as = "abcdefghijklmnopqrstuvwxyz", ai[128];
char *ah = "22233344455566677778889999", di[128];

char ls[1000][101];
int w, d, l;
int wb[99927][26], wp[99927];
int db[33309][8], dp[33309], dl[33309];

void ins(char* s, int p) {
	int i, j, wt, dt;
	for (i = wt = dt = 0; s[i]; i++) {
		if (!wb[wt][ai[s[i]]]) {
			wb[wt][ai[s[i]]] = ++w;
			wp[w] = 0;
			for (j = 0; j < 26; j++)
				wb[w][j] = 0;
		}

		if (!db[dt][di[s[i]]]) {
			db[dt][di[s[i]]] = ++d;
			dp[d] = 0;
			for (j = 0; j < 8; j++)
				db[d][j] = 0;
		}

		wt = wb[wt][ai[s[i]]];
		dt = db[dt][di[s[i]]];

		wp[wt] += p;
		if (wp[wt] > dp[dt]) {
			dp[dt] = wp[wt];
			dl[dt] = l;
		}
	}

	sprintf(ls[l++], s);
}

void tap(char* s) {
	int i, j, dt;
	for (i = dt = 0; s[i] > '1'; i++) {
		if (!db[dt][di[s[i]]])
			break;

		dt = db[dt][di[s[i]]];

		for (j = 0; j <= i; j++)
			putchar(ls[dl[dt]][j]);
		puts("");
	}

	for (; s[i] > '1'; i++)
		puts("MANUALLY");
}

int main() {
	int c, n, p, i;
	char s[128];

	for (p = 0; as[p]; p++) {
		ai[as[p]] = p;
		di[as[p]] = ah[p] - '2';
		di[ah[p]] = ah[p] - '2';
	}

	scanf("%d", &c);
	for (i = 1; i <= c; i++) {
		w = d = l = 0;
		for (p = 0; p < 8; p++) db[0][p] = wb[0][p] = 0;
		for (; p < 26; p++) wb[0][p] = 0;
		printf("Scenario #%d:\n", i);

		scanf("%d", &n);
		while (n--) {
			scanf(" %s %d", s, &p);
			ins(s, p);
		}

		scanf("%d", &n);
		while (n--) {
			scanf(" %s", s);
			tap(s);
			puts("");
		}
		puts("");
	}

	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值