zoukankan      html  css  js  c++  java
  • trie树及其可持久化操作学习笔记

    trie树(字典树)

    例题于是他错误的点名开始了

    #include <cstdio>
    #include <algorithm>
    #include <cstring>
    
    using namespace std;
    
    struct node
    {
    	bool wrd, vis;
    	int ch[26];
    }trie[500009]; 
    int siz = 1;
    
    inline void insert(char* str)
    {
    	int t = 1, len = strlen(str);
    	for(int i = 0; i < len; i++)
    	{
    		if(!trie[t].ch[str[i] - 'a'])
    			t = trie[t].ch[str[i] - 'a'] = ++siz;
    		else
    			t = trie[t].ch[str[i] - 'a'];
    	}
    	trie[t].wrd = true;
    }
    
    inline int search(char* str)
    {
    	int t = 1, len = strlen(str);
    	for(int i = 0; i < len; i++)
    	{
    		if(!trie[t].ch[str[i] - 'a'])
    			return 0;
    		else 
    			t = trie[t].ch[str[i] - 'a'];
    	}
    	if(trie[t].wrd)
    	{
    		if(trie[t].vis) return -1;
    		trie[t].vis = true;
    		return 1;
    	}
    	else return 0;
    }
    
    int n, m;
    char str[55];
    
    int main() {
    	scanf("%d", &n);
    	for(int i = 1; i <= n; i++)
    	{
    		scanf("%s", str);
    		insert(str);
    	}
    	scanf("%d", &m);
    	for(int i = 0; i < m; i++)
    	{
    		scanf("%s", str);
    		int res = search(str);
    		if(res == 1) printf("OK
    ");
    		else if(res == -1) printf("REPEAT
    ");
    		else printf("WRONG
    ");
    	}
    	return 0;
    } 
    

    前置

    大概没有
    

    原理

    结构

    trie

    如上图,这就是一棵Trie树。这个Trie树中包含的单词有abcabdabcdbbcdefghij。可以看到,Trie的字母并不存在结点上,而是存在边上。每个结点的儿子表示以这个结点对应的前缀的字符串的延续。结点的标记代表字符串的终止。这就是一棵Trie树的结构。

    查找

    从节点开始逐字符下跳, 直到没有对应的字符出现或在无结束标记情况下终止,代表查找失败

    反之一直有对应字符并且在有标记情况下结束代表查找成功

    插入

    和查找相同的下跳,如果出现了没有的字符就创建新节点,并在结束时打上结束标记

    分步实现

    准备

    一个结构体储存信息, wrd表示结束标记,siz表示当前节点数

    struct node
    {
    	bool wrd, vis;
    	int ch[26];
    }trie[500009]; 
    int siz = 1;
    

    查找

    vis可以判断是否找过

    inline int search(char* str)
    {
    	int t = 1, len = strlen(str);
    	for(int i = 0; i < len; i++)
    	{
    		if(!trie[t].ch[str[i] - 'a'])
    			return 0;
    		else 
    			t = trie[t].ch[str[i] - 'a'];
    	}
    	if(trie[t].wrd)
    	{
    		if(trie[t].vis) return -1;
    		trie[t].vis = true;
    		return 1;
    	}
    	else return 0;
    }
    

    插入

    inline void insert(char* str)
    {
    	int t = 1, len = strlen(str);
    	for(int i = 0; i < len; i++)
    	{
    		if(!trie[t].ch[str[i] - 'a'])
    			t = trie[t].ch[str[i] - 'a'] = ++siz;
    		else
    			t = trie[t].ch[str[i] - 'a'];
    	}
    	trie[t].wrd = true;
    }
    

    然后就没了


    11.4更新

    可持久化trie树

    可持久化字典树就是记录 在字典树上有相同前缀 的前缀和(节点的个数),然后通过取差值(右边界减去左边界)判断一段区间内是否有字典树上的前缀

    类似于主席树(毕竟都是可持久化操作)

    求异或的最大值可以用01-trie树解决(用相同的深度存树,查询时从最高位贪心)

    因此区间求异或最大值我们一般选择可持久化01-trie树

    例题1:

    novaoj

    概述:求区间异或最大值

    显然直接暴力不能a,考虑可持久化tire树

    插入

    void insert(ll x, ll t, ll &z) {//t为上一个版本,z为当前版本
    	z = ++siz;//记录根节点
    	trie[z].init();//初始化
    	trie[z].sum = trie[t].sum + 1;//节点数++
    	ll k = z;
    	for(ll j = 31; j >= 0; j--) {//按位存入新的版本
    		bool nx = ((1LL * 1 << j) & x);
    		trie[k].ch[nx ^ 1] = trie[t].ch[nx ^ 1];//当前版本与上一个版本重复的地方直接指向历史版本以节约空间
    		trie[k].ch[nx] = ++siz;
    		k = siz;
    		t = trie[t].ch[nx];
    		trie[k].sum = trie[t].sum + 1;//节点数
    	}
    }
    

    查询

    ll search(ll l, ll r, ll x) {
    	ll ans = 0;
    	for(ll j = 31; j >= 0; j--) {
    		bool nx = ((1LL * 1 << j) & x);//按位查询
    		ll nl = trie[l].ch[nx ^ 1];
    		ll nr = trie[r].ch[nx ^ 1];//
    		ll z = trie[nr].sum - trie[nl].sum;//如果z > 0判断是否存在(存在说明当前的x异或后可以找到一个1)
    		if(z) {
    			ans += 1LL * 1 << j;//累计答案
    			l = nl, r = nr;
    		}
    		else {
    			l = trie[l].ch[nx];
    			r = trie[r].ch[nx];
    		}
    	}
    	return ans;
    }
    

    代码

    #include <cstdio>
    #include <algorithm>
    #include <cstring>
    
    #define ll long long
    
    ll n, q;
    
    struct node {
    	ll ch[3], sum;
    //	bool wrd;
    	void init() {
    		sum = 0;
    		memset(ch, 0, sizeof(ch));
    	}
    }trie[200000 * 40];
    
    ll siz, root[600000];
    
    void init() {
    	memset(trie, 0, sizeof(trie));
    	siz = 0;
    	memset(root, 0, sizeof(root));
    }
    
    void insert(ll x, ll t, ll &z) {
    	z = ++siz;
    	trie[z].init();
    	trie[z].sum = trie[t].sum + 1;
    	ll k = z;
    	for(ll j = 31; j >= 0; j--) {
    		bool nx = ((1LL * 1 << j) & x);
    		trie[k].ch[nx ^ 1] = trie[t].ch[nx ^ 1];
    		trie[k].ch[nx] = ++siz;
    		k = siz;
    		t = trie[t].ch[nx];
    		trie[k].sum = trie[t].sum + 1;
    	}
    }
    
    ll search(ll l, ll r, ll x) {
    	ll ans = 0;
    	for(ll j = 31; j >= 0; j--) {
    		bool nx = ((1LL * 1 << j) & x);
    		ll nl = trie[l].ch[nx ^ 1];
    		ll nr = trie[r].ch[nx ^ 1];
    		ll z = trie[nr].sum - trie[nl].sum;
    		if(z) {
    			ans += 1LL * 1 << j;
    			l = nl, r = nr;
    		}
    		else {
    			l = trie[l].ch[nx];
    			r = trie[r].ch[nx];
    		}
    	}
    	return ans;
    }
    
    int main() {
    	freopen("hugclose.in", "r", stdin);
    	freopen("hugclose.out", "w", stdout);
    	scanf("%lld%lld", &n, &q);
    	init();
    	for(ll i = 1; i <= n; i++) {
    		ll a;
    		scanf("%lld", &a);
    		insert(a, root[i - 1], root[i]);
    	}
    	for(ll j = 1; j <= q; j++) {
    		ll l, r, x;
    		scanf("%lld%lld%lld", &x, &l, &r);
    		printf("%lld
    ", search(root[l], root[r + 1], x));
    	}
    	return 0;
    } 
    

    例题2

    最大异或和

    异或有可减性,因此可以求出前缀异或和,以此构建trie树

    (本题luogu需要玄学卡常,也可能是我trie树常熟实在太大)

    #include <cstdio>
    #include <algorithm>
    #include <cstring>
    
    const int MAXN = 600000;
    
    using namespace std;
    
    int n, q, root[MAXN * 23 + 200000], siz, ret[MAXN + 7];//数组范围再开大会T
    
    struct node {
    	int ch[2], sum;
    	void init() {
    		memset(ch, 0, sizeof(ch));
    		sum = 0;
    	}
    }trie[MAXN * 23 + 300000];
    
    inline void init() {
    	memset(trie, 0, sizeof(trie));
    	siz = 0;
    	memset(root, 0, sizeof(root));
    }
    
    inline void insert(int x, int t, int &z) {
    	z = ++siz;
    	trie[z].init();
    	trie[z].sum = trie[t].sum + 1;
    	int k = z;
    	for(register int i = 23; i >= 0; i--) {
    		bool nx = ((1LL * 1 << i) & x);
    		trie[k].ch[nx ^ 1] = trie[t].ch[nx ^ 1];
    		trie[k].ch[nx] = ++siz;
    		k = siz;
    		t = trie[t].ch[nx];
    		trie[k].sum = trie[t].sum + 1;
    	}
    }
    
    inline int search(int l, int r, int x){
    	int ans = 0;
    	for(register int i = 23; i >= 0; i--) {
    		bool nx = ((1LL * 1 << i) & x);
    		int nl = trie[l].ch[nx ^ 1];
    		int nr = trie[r].ch[nx ^ 1];
    		int z = (trie[nr].sum - trie[nl].sum);
    		if(z) {
    			ans += (1LL * 1 << i);
    			l = nl, r = nr;
    		}
    		else {
    			l = trie[l].ch[nx];
    			r = trie[r].ch[nx];
    		}
    	}
    	return ans;
    }
    
    int main() {
    	int x;
    	scanf("%d%d", &n, &q);
    	init();
    	insert(ret[1], root[1], root[1]);//空节点插入
    	for(register int i = 2; i <= n + 1; i++) {
    		scanf("%d", &x);
    		ret[i] = ret[i - 1] ^ x;//前缀和
    		insert(ret[i], root[i - 1], root[i]);
    	}
    	int h = n + 1;
    	for(register int i = 1; i <= q; i++) {
    		char op;
    		int l, r, x;
    		scanf("
    ");
    		scanf("%c", &op);
    		if(op == 'A') {
    			scanf("%d", &x);
    			++h;
    			ret[h] = ret[h - 1] ^ x;
    			insert(ret[h], root[h - 1], root[h]);
    		}
    		else {
    			scanf("%d%d%d", &l, &r, &x);
    			printf("%d
    ", search(root[l - 1], root[r], ret[h] ^ x));
    		}
    	}
    }
    
  • 相关阅读:
    eslint 的 env 配置是干嘛使的?
    cookie httpOnly 打勾
    如何定制 antd 的样式(theme)
    剑指 Offer 66. 构建乘积数组
    剑指 Offer 65. 不用加减乘除做加法
    剑指 Offer 62. 圆圈中最后剩下的数字
    剑指 Offer 61. 扑克牌中的顺子
    剑指 Offer 59
    剑指 Offer 58
    剑指 Offer 58
  • 原文地址:https://www.cnblogs.com/wyswyz/p/11312816.html
Copyright © 2011-2022 走看看