初探字典树、Trie树 题目整理

字典树

字典树是一种处理前缀的数据结构
略懂数据结构的人,相信看完下面这张图就差不多理解了
在这里插入图片描述

  • TrieTrie的根节点是空的(相信没有题目给的所有数据有公共前缀)
  • 每个节点储存一个单词/字母
  • 根节点到每个单词节点的路径上的所有字母连接而成的字符串就是该节点对应的字符串
  • 空间换时间的方法(1秒一般能解决的总字符数量在100000个,空间一般也开800000,适用于查询比较多的情况)

实现

以小写字母为例,讲解以下代码实现

节点

需要一个数组存储节点信息
外加一个mark标志,存储答案

struct Tree{
	Tree() {		//构造函数
		mark = 0;
		memset(son, 0, sizeof(son));
	}
	void clear() {
		mark = 0;
		memset(son, 0, sizeof(son));
	}
	int mark;		//标记,一般为询问的值,视情况而定
	int son[26];	//此处只考虑小写字母,即为节点存储的数据
}tree[maxn];

插入

逐层迭代,将没有的节点插入
在末尾节点的下一个节点存储该字符串的信息(因为由这个节点前面构成的字符串是唯一的)

int root, num;		//根节点永久为0
void insert(char* str) {
	int position = root;				//初始化位置
	for (int i = 0; str[i]; i++) {
		int symbol = str[i] - 'a';		//转化函数,视情况而定
		if (!tree[position].son[symbol])//创建新节点
			tree[position].son[symbol] = ++num;
		position = tree[position].son[symbol];
	}
	tree[position].mark++;				//记录链末尾的数量
}

查询

逐层递归查找

int find(char* str) {
	int position = root;
	for (int i = 0; str[i]; i++) {
		int symbol = str[i] - 'a';
		if (!tree[position].son[symbol]) //找不到,返回false
			return 0;
		position = tree[position].son[symbol];//迭代寻找
	}
	return tree[position].mark;		//返回相同链的数量
}

实际上,我们发现字典树是种很简单的数据结构,上面的插入和查询只是基本操作
当你理解上面的插入和查询操作后,也可以轻松实现其他操作

例题

P2580 于是他错误的点名开始了

题意

给出一组名字(好几个字符串)
进行m次点名,第一次字符串出现输出“OK”,第二次以上出现输出“REPEAT”,若该字符串不在给出字符串内输出“WRONG”

思路

板子题+1
mark记录该字符串是否出现过
记录该字符串被访问次数

P3879 [TJOI2010]阅读理解

题意

给出n组字符串,每组字符串有1个或多个字符串
有m组询问,询问为一个字符串,返回在哪几组字符串中出现过

思路

板子题+2
用vector存储一个字符串在哪几组字符串中出现过

P5149 会议座位

题意

按顺序给出nn个字符串
第一个字符串最小,第二个第二,以此类推
再次给出n个字符的顺序,求逆序对数

思路

板子题+3
字典树离散化字符串
归并排序求逆序对数

P3294 [SCOI2016]背单词

题意

给你n个字符串,不同的排列有不同的代价,代价按照如下方式计算(字符串s的位置为x)
1.排在s后面的字符串有s的后缀,则代价为n^2
2.排在s前面的字符串有s的后缀,且没有排在s后面的s的后缀,则代价为x-y(y为最后一个与s不相等的后缀的位置);
3.s没有后缀,则代价为x
求最小代价和

思路

显然第一种代价是不可能被用到的
只要按后缀逐个取字符就能避免第一种情况

处理后缀比较麻烦,我们将字符串翻转,就可以用字典树存储前缀
按前缀逐个取字符串

容易证明先取后缀少的字符串,最终代价少

先重构树,只保留存在的字符串

dfsdfs处理各个子树的节点数
再根据子树权重,优先对权重小的子树搜索即可
//具体过程,类似树链剖分

例题代码

P2580 于是他错误的点名开始了

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
#pragma warning (disable:4996)
const int maxn = 1000005;
struct Tree{
	Tree() {
		mark = 0;
		memset(son, 0, sizeof(son));
	}
	void clear() {
		mark = 0;
		memset(son, 0, sizeof(son));
	}
	int mark;		//标记
	int son[26];	//此处只考虑小写字母
}tree[maxn];
int root, num;		//根节点永久为0
void insert(char* str) {
	int position = root;				//初始化位置
	for (int i = 0; str[i]; i++) {
		int symbol = str[i] - 'a';		//转化函数,视情况而定
		if (!tree[position].son[symbol])//创建新节点
			tree[position].son[symbol] = ++num;
		position = tree[position].son[symbol];
	}
	tree[position].mark = 1;			//记录链末尾的数量
}
int find(char* str) {
	int position = root;
	for (int i = 0; str[i]; i++) {
		int symbol = str[i] - 'a';
		if (!tree[position].son[symbol]) //找不到,返回false
			return 0;
		position = tree[position].son[symbol];//迭代寻找
	}
	return tree[position].mark++;		//返回相同链的数量
}
char s[65];
int main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	cout.tie(nullptr);
	int n, m; cin >> n;
	for (int i = 1; i <= n; i++) {
		cin >> s;
		insert(s);
	}
	cin >> m;
	while (m--) {
		cin >> s;
		int res = find(s);
		if (res == 0)cout << "WRONG\n";
		else if (res == 1)cout << "OK\n";
		else cout << "REPEAT\n";
	}
}

P3879 [TJOI2010]阅读理解

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <vector>
using namespace std;
#pragma warning (disable:4996)
const int maxn = 1000005;
struct Tree{
	Tree() {
		memset(son, 0, sizeof(son));
	}
	void clear() {
		id.clear();
		memset(son, 0, sizeof(son));
	}
	vector<int> id;
	int son[26];	//此处只考虑小写字母
}tree[maxn];
int root, num;		//根节点永久为0
void insert(char* str, int x) {
	int position = root;				//初始化位置
	for (int i = 0; str[i]; i++) {
		int symbol = str[i] - 'a';		//转化函数,视情况而定
		if (!tree[position].son[symbol])//创建新节点
			tree[position].son[symbol] = ++num;
		position = tree[position].son[symbol];
	}
	if (tree[position].id.size() == 0 ||
		x != tree[position].id[tree[position].id.size() - 1])
		tree[position].id.push_back(x);
}
void find(char* str) {
	int position = root;
	for (int i = 0; str[i]; i++) {
		int symbol = str[i] - 'a';
		if (!tree[position].son[symbol]) //找不到,返回false
		{
			printf("\n");
			return;
		}
		position = tree[position].son[symbol];//迭代寻找
	}
	for (int i = 0; i < tree[position].id.size(); i++)
		if (i)printf(" %d", tree[position].id[i]);
		else printf("%d", tree[position].id[i]);
	printf("\n");
}
char s[30];
int main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	cout.tie(nullptr);
	int n, m; cin >> n;
	for (int i = 1; i <= n; i++) {
		cin >> m;
		while (m--) {
			cin >> s;
			insert(s, i);
		}
	}
	cin >> m;
	while (m--) {
		cin >> s;
		find(s);
	}
}

P5149 会议座位

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
#pragma warning (disable:4996)
typedef long long LL;
const int maxn = 100005;
struct Tree{
	Tree() {
		mark = 0;
		memset(son, 0, sizeof(son));
	}
	void clear() {
		mark = 0;
		memset(son, 0, sizeof(son));
	}
	int mark;		//标记
	int son[52];	//此处只考虑小写字母
}tree[maxn << 2];
int root, num;		//根节点永久为0
void insert(char* str, int x) {
	int position = root;				//初始化位置
	for (int i = 0; str[i]; i++) {
		int symbol;
		if (str[i] >= 'A' && str[i] <= 'Z')symbol = str[i] - 'A';
		else symbol = str[i] - 'a' + 26;
		if (!tree[position].son[symbol])//创建新节点
			tree[position].son[symbol] = ++num;
		position = tree[position].son[symbol];
	}
	tree[position].mark = x;				//记录链末尾的数量
}
int find(char* str) {
	int position = root;
	for (int i = 0; str[i]; i++) {
		int symbol;
		if (str[i] >= 'A' && str[i] <= 'Z')symbol = str[i] - 'A';
		else symbol = str[i] - 'a' + 26;
		if (!tree[position].son[symbol]) //找不到,返回false
			return 0;
		position = tree[position].son[symbol];//迭代寻找
	}
	return tree[position].mark;		//返回相同链的数量
}
char s[20];
int a[maxn];
LL ans = 0;
void merge(int left, int right, int stdl, int stdr) {
	if (left == stdr) return;
	int i = left, j = stdl, k = 0;
	int* t = new int[stdr - left + 1];
	while (i <= right && j <= stdr) {
		if (a[i] < a[j]) t[k++] = a[i++];
		else {
			t[k++] = a[j++];
			ans = ans + right - i + 1;
		}
	}
	while (i <= right)t[k++] = a[i++];
	while (j <= stdr)t[k++] = a[j++];
	for (int i = left; i <= stdr; i++)a[i] = t[i - left];
	delete []t;
}
void merge_sort(int left, int right) {
	if (left == right) return;
	int mid = (left + right) >> 1;
	merge_sort(left, mid);
	merge_sort(mid + 1, right);
	merge(left, mid, mid + 1, right);
}
int main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	cout.tie(nullptr);
	int n; cin >> n;
	for (int i = 1; i <= n; i++) {
		cin >> s;
		insert(s, i);
	}
	for (int i = 1; i <= n; i++) {
		cin >> s;
		a[i] = find(s);
	}
	merge_sort(1, n);
	printf("%lld\n", ans);
}

P3294 [SCOI2016]背单词

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <vector>
using namespace std;
#pragma warning (disable:4996)
typedef long long LL;
const int maxn = 100005;
struct Tree{
	Tree() {
		mark = 0;
		memset(son, 0, sizeof(son));
	}
	void clear() {
		mark = 0;
		memset(son, 0, sizeof(son));
	}
	bool mark;		//标记
	int son[26];	//此处只考虑小写字母
}tree[maxn << 3];
int root, num;		//根节点永久为0
void insert(char* str) {
	int position = root;				//初始化位置
	for (int i = 0; str[i]; i++) {
		int symbol = str[i] - 'a';		//转化函数,视情况而定
		if (!tree[position].son[symbol])//创建新节点
			tree[position].son[symbol] = ++num;
		position = tree[position].son[symbol];
	}
	tree[position].mark = true;				//记录链末尾的数量
}
int find(char* str) {
	int position = root;
	for (int i = 0; str[i]; i++) {
		int symbol = str[i] - 'a';
		if (!tree[position].son[symbol]) //找不到,返回false
			return 0;
		position = tree[position].son[symbol];//迭代寻找
	}
	return tree[position].mark;		//返回相同链的数量
}
char s[maxn * 6];
vector<int> E[maxn]; int cnt;
void struct_dfs(int root, int f) {
	for (int i = 0; i < 26; i++) {
		if (!tree[root].son[i]) continue;
		if (!tree[tree[root].son[i]].mark)
			struct_dfs(tree[root].son[i], f);
		else {
			E[f].push_back(++cnt);
			struct_dfs(tree[root].son[i], cnt);
		}
	}
}
int n_size[maxn];
bool cmp(int x, int y) {
	return	n_size[x] < n_size[y];
}
void dfs1(int now, int f) {
	n_size[now] = 1;
	for (int i = 0; i < E[now].size(); i++) {
		dfs1(E[now][i], now);
		n_size[now] += n_size[E[now][i]];
	}
	sort(E[now].begin(), E[now].end(), cmp);
}
int dfn, d[maxn];
LL ans = 0;
void dfs2(int now, int f) {
	d[now] = ++dfn;
	ans = ans + d[now] - d[f];
	for (int i = 0; i < E[now].size(); i++)
		dfs2(E[now][i], now);
}
int main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	cout.tie(nullptr);
	int n; cin >> n;
	while (n--) {
		cin >> s;
		reverse(s, s + strlen(s));
		insert(s);
	}
	struct_dfs(0, 0);
	dfs1(0, -1);
	dfs2(0, 0);
	cout << ans << '\n';
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章