[模板]前缀树 / 字典树及应用

前缀树 / 字典树是最简单的树了

欠的总是要还的

O(n)算法的多叉树

比较好理解没啥解释的了

用数组模拟 行结点 列指针 cnt记录最大结点编号

直接上代码吧

可持久化01字典树



struct trie
{
	int cnt, root[MAXN], tr[MAXN * 25][2], sum[MAXN * 25];
//结点总个数 版本进入根        树      结点内个数前缀和  
	trie() {cnt = 0;}
	
	void insert(int now, int last, int val)  //这一版本根下标 上一版本根下标 
	{
		int nto, lto;
		root[now] = nto = ++cnt;
		lto = root[last];
		for(int i = 23; i >= 0; --i)
		{
			int dig = val >> i & 1;
			tr[nto][0] = tr[lto][0];
			tr[nto][1] = tr[lto][1];  //将上一版本儿子直接覆盖 
			tr[nto][dig] = ++cnt, nto = cnt; //当前版本的新儿子 
			lto = tr[lto][dig]; 
			sum[nto] = sum[lto] + 1; //前缀和 + 1 
		}
	}
	
	int get(int now, int last, int val) //这一版本根下标 上一版本根下标 
	{
		int ret = 0;
		int nto = root[now], lto = root[last];
		for(int i = 23; i >= 0; --i)
		{
			int dig = val >> i & 1 ^ 1;
			if(sum[ tr[nto][dig] ] - sum[ tr[lto][dig] ]) //当前区间内有该目标节点 
				ret += (1 << i), nto = tr[nto][dig], lto = tr[lto][dig];
			else
				nto = tr[nto][dig ^ 1], lto = tr[lto][dig ^ 1];
		}
		return ret;
	}
}T;

int arr[MAXN];

int main()
{  
	for(int i = 2, t; i <= n; ++i)
		T.insert(i, i - 1, arr[i]);
		
	cout << T.get(y, x - 1, arr[n] ^ val) << '\n';

    return 0;
}

1.统计单词数 -> 建树
 

int trie[MAXN][26] = {0}, cnt = 1, ans = 0;    //trie树 行结点 列指针

bool wend[MAXN] = {false};        //保存以i节点为结尾的单词是否存在

void insert(char word[])
{
    int len = strlen(word), to = 1;
    
    for(int i = 0; i < len; i++)
    {
        int chn = word[i] - 'a';
        if(!trie[to][chn])
        	trie[to][chn] = ++cnt;
        to = trie[to][chn];
    }

    if(!wend[to])    //计和
        ++ans;

    wend[to] = true;
}

2.单词查询 -> 查询是否能在树中完全匹配

bool wend[MAXN] = {false};        //保存以i节点为结尾的单词是否存在

bool intree(char word[])
{
    int len = strlen(word), to = 1;

    for(int i = 0; i < len; i++)
    {
        int chn = word[i] - 'a';
        if(! trie[to][chn])
            return false;
        to = trie[to][chn];
    }
    return wend[to];  //查询以i节点为结尾的单词是否存在
}

3.存在前缀 -> 查询是否能在树中前缀匹配

例题: http://codevs.cn/problem/4189/

bool intree(char word[]) //只要能扫到单词结尾则必定存在前缀
{
    int len = strlen(word), to = 1;

    for(int i = 0; i < len; i++)
    {
        int chn = word[i] - 'a';
        if(! trie[to][chn])
            return false;
        to = trie[to][chn];
    }
    return true;
}

4.前缀统计1 -> 此单词为字典中多少单词的前缀的和

例题: http://acm.hdu.edu.cn/showproblem.php?pid=1251

int trie[MAXN][26] = {0}, sum[MAXN] = {0}, cnt = 1; //sum数组保存到此结点的前缀总和

bool wend[MAXN] = {false};

void insert(char word[])
{
    int len = strlen(word), to = 1;
    
    for(int i = 0; i < len; i++)
    {
        int chn = word[i] - 'a';
        if(!trie[to][chn])
        	trie[to][chn] = ++cnt;
        to = trie[to][chn];
        ++sum[to];                         //计和
    }

    wend[to] = true;
}

int precnt(char word[])
{
    int len = strlen(word), to = 1;

    for(int i = 0; i < len; i++)
    {
        int chn = word[i] - 'a';
        if(!trie[to][chn])
            return 0;                //前缀都扫不完查无此单词 和就不用想了
        to = trie[to][chn];
    }
    
    return sum[to];
}

5.前缀统计2 -> 统计树中有多少单词为当前单词的前缀

例题:http://acm.ncst.edu.cn/problem.php?pid=1421

const int MAXN = 1e6 + 10;

int trie[MAXN][26] = {0}, cnt = 1;

int wend[MAXN] = {0}; //wend本用来标记单词结尾, 现在用来标记以该结点结尾的单词的数量

void insert(char word[])
{
    int len = strlen(word), to = 1;
    
    for(int i = 0; i < len; i++)
    {
        int chn = word[i] - 'a';
        if(!trie[to][chn])
        	trie[to][chn] = ++cnt;
        to = trie[to][chn];
    }

    ++wend[to];
}

int precnt(char word[])
{
    int len = strlen(word), to = 1, ret = 0;

    for(int i = 0; i < len; i++)
    {
        int chn = word[i] - 'a';
        if(!trie[to][chn])
            break;
        to = trie[to][chn];
        ret += wend[to];    //路过的所有结点均为word的前缀 所以加和计算
    }
    
    return ret;
}


 

posted @ 2019-03-14 17:03  张浦  阅读(109)  评论(0编辑  收藏  举报