POJ 2846 解题报告 Trie 树变型

题目意思:

给一个待查询的字符串,输出包含这个字符串(作为子串)的个数。


数据量比较大,用 KMP 会超时。可以用 Trie 树,不过需要变型。

正常的 Trie 树是以前缀开始的。在这题需要改成以后缀开始,后缀指以字符串每个字符作为开始直至结尾。例如字符串 abcdae ,各个后缀是 abcdae,bcdae,cdae,dae,ae,e。这样就很方便查询子串出现的次数了。

但是要记录每个后缀它是属于哪个源字符串的。不然在 insert 时会重复计算。所以可以给每个结点增加一个 id 的字符,标识源字符串是第几个。


代码:

#include <cstdio>
#include <cstring>

#define maxn 100010 //10万
#define wlen 20 //单词长度
#define character 'a'
#define SIZE 26  //26个字母

typedef struct node {
	int cnt; //记录访问量
	int id; //记录是否同一个源字符串
	int childs[SIZE]; 
	void init()
	{
		id = -1;
		cnt = 0;
		memset(childs, 0, sizeof(childs));
	}
} Node;

Node ns[maxn * wlen];
int root;
int node_cnt = 0; //trie 树的结点数
char input[30];

void insert(char *s, int id)
{//建立以后缀为主的 trie 树,例如字符串 abcdae ,各个后缀是 abcdae,bcdae,cdae,dae,ae,e
 //避免重复计算,例如 abcdae 和 ae 两个子串。所以要增加一个 id 标识是否同一个源字符串。
	int r = 0, index, child;
	for (int i = 0; s[i] != '\0'; ++i) {
		index = s[i] - character;
		child = ns[r].childs[index];
		if (child != 0) {
			r = child;
		}
		else {
			ns[++node_cnt].init();
			ns[r].childs[index] = node_cnt;
			r = node_cnt;
		}
		if (ns[r].id != id) ns[r].cnt++;
		ns[r].id = id;
	}
}

int query(char *s)
{
	int r = 0;
	int index, child;
	for (int i = 0; s[i] != '\0'; ++i) {
		index = s[i] - character;
		child = ns[r].childs[index];
		if (child == 0) {
			return 0;
		}
		r = child;
	}
	return ns[r].cnt;
}

int main()
{
	//freopen("testdata/2846.txt", "r", stdin);
	int p, q;
	ns[node_cnt = 0].init();
	scanf("%d", &p);
	while (p--) {
		scanf("%s", input);
		for (int i = 0; i < strlen(input); ++i) {
			insert(input+i, p+1);
		}
	}
	scanf("%d", &q);
	while (q--) {
		scanf("%s", input);
		printf("%d\n", query(input));
	}
	return 0;
}




你可能感兴趣的:(tree,ACM,poj,trie)