• 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));
    		}
    	}
    }
    
  • 相关阅读:
    shell命令--stat
    英文段子
    OCP读书笔记(16)
    shell命令--uptime
    OCP读书笔记(15)
    shell命令--dmesg
    OCP读书笔记(14)
    shell命令--hostname
    OCP读书笔记(13)
    shell命令--uname
  • 原文地址:https://www.cnblogs.com/wyswyz/p/11312816.html
Copyright © 2020-2023  润新知