Trie树模板 + 例题

模板题—前缀统计

题目描述

给定N个字符串S1,S2…SN,接下来进行M次询问,每次询问给定一个字符串T,求S1~SN中有多少个字符串是T的前缀。
输入字符串的总长度不超过106,仅包含小写字母。
输入格式
第一行输入两个整数N,M。
接下来N行每行输入一个字符串Si。
接下来M行每行一个字符串T用以询问。
输出格式
对于每个询问,输出一个整数表示答案。
每个答案占一行。
输入样例:
3 2
ab
bc
abc
abc
efg
输出样例:
2
0

代码1–数组模拟前缀树

#include 

using namespace std;

const int N = 1000010, M = 500010;
int son[M][26], cnt[M], idx;
char str[N];
int n, m;

void insert()
{
    int p = 0; //根节点
    for(int i = 0; str[i]; i++)
    {
        int &s = son[p][str[i] - 'a'];
        if(!s) s = ++idx; //分配虚拟内存,表示这个节点已经有了
        p = s;
    }
    
    cnt[p]++; //表示以p结尾的字符串的个数
}

int query()
{
    int p = 0, res = 0;
    for(int i = 0; str[i]; i++)
    {
        int &s = son[p][str[i] - 'a'];
        if(!s) break; //表示没有找到
        p = s;
        res += cnt[p];
    }
    
    return res;
    
}

int main()
{
    scanf("%d%d", &n, &m);
    while(n--)
    {
        scanf("%s", str);
        insert();
    }
    
    while(m--)
    {
        scanf("%s", str);
        printf("%d\n", query());
    }
    
    return 0;
}

代码2–直接建建立前缀树

#include 

using namespace std;

const int N = 1000010;
char str[N];
int n, m;

class Trie
{
private:
    int cnt = 0;
    Trie *next[26] = {nullptr};
public:
    Trie(){}
    
    void insert()
    {
        Trie *root = this;
        for(int i = 0; str[i]; i++)
        {
            if(root->next[str[i] - 'a'] == nullptr) root->next[str[i] - 'a'] = new Trie();
            root = root->next[str[i] - 'a'];
        }
        
        root->cnt++;
    }
    
    int query()
    {
        Trie *root = this;
        int res = 0;
        for(int i = 0; str[i]; i++)
        {
            if(root->next[str[i] - 'a'] == nullptr) break;
            root = root->next[str[i] - 'a'];
            res += root->cnt;
        }
        
        return res;
    }
};

int main()
{
    Trie *trie = new Trie();
    scanf("%d%d", &n, &m);
    while(n--)
    {
        scanf("%s", str);
        trie->insert();
    }
    
    while(m--)
    {
        scanf("%s", str);
        printf("%d\n", trie->query());
    }
    
    return 0;
}

应用-- 最大异或对

题目描述

在给定的N个整数A1,A2……AN中选出两个进行xor(异或)运算,得到的结果最大是多少?
输入格式
第一行输入一个整数N。
第二行输入N个整数A1~AN。
输出格式
输出一个整数表示答案。
数据范围
1≤N≤105,
0≤Ai<231
输入样例:
3
1 2 3
输出样例:
3

题解

  1. 暴力法:双重循环枚举所有数对,找到最大值。
  2. 前缀树优化掉第二层循环。思路:每个数有31位, 从高位到低位依次比较每一位,保证相同位是不同的值,就可以保证异或值最大。
  3. 所以就可以用前缀树来储存所有数的31位(用前缀树的原因:每个数二进制只有两个数0和1,用前缀树可以增加查询效率)。然后选一个数从高位起,在前缀树里找相应位不同的数,如果有,则可以累加答案,如果没有就不用累加答案。

代码

#include 
#include 
using namespace std;

const int N = 100010, M = 3100010;
int son[M][2], idx;
int a[N];
int n;

void insert(int x)
{
    int p = 0;
    for(int i = 30; i >= 0; i--)
    {
        int &s = son[p][x >> i & 1];
        if(!s) s = ++idx;
        p = s;
    }
}

int query(int x)
{
    int p = 0, res = 0;
    for(int i = 30; i >= 0; i--)
    {
        int s = x >> i & 1;
        if(son[p][!s]) //下一个不同存在
        {
            res += 1 << i; //累加这一位
            p = son[p][!s];
        }
        else p = son[p][s];
    }
    
    return res;
    
}

int main()
{
    scanf("%d", &n);
    
    for(int i = 0; i < n; i++)
    {
        cin >> a[i];
        insert(a[i]);
    }
    
    int res = 0;
    
    for(int i = 0; i < n; i++) res = max(res, query(a[i]));
    
    cout << res << endl;
    
    return 0;
}



应用-- 最大异或值路径

题目描述

给定一个树,树上的边都具有权值。
树中一条路径的异或长度被定义为路径上所有边的权值的异或和:
⊕ 为异或符号。
给定上述的具有n个节点的树,你能找到异或长度最大的路径吗?
输入格式
第一行包含整数n,表示树的节点数目。
接下来n-1行,每行包括三个整数u,v,w,表示节点u和节点v之间有一条边权重为w。
输出格式
输出一个整数,表示异或长度最大的路径的最大异或和。
数据范围
1≤n≤100000,
0≤u,v 0≤w<231
输入样例:
4
0 1 3
1 2 4
1 3 6
输出样例:
7
样例解释
样例中最长异或值路径应为0->1->2,值为7 (=3 ⊕ 4)

题解

  1. 异或性质:将一个数加入集合或从集合删除,都可以用异或。将3加入集合:2^3, 将3去掉:
    3^3 = 0 2^0 = 2.
    Trie树模板 + 例题_第1张图片
    Trie树模板 + 例题_第2张图片
    问题就转化为了求出每个点到根节点路径上的异或值。然后就转化成了上面的题目。

代码

#include 
#include 
#include 

using namespace std;

const int N = 100010, M = 30000010;
int son[M][2], idx;
int h[N], e[M], w[M], ne[M], cnt;
int a[N];
int n;

void add(int a, int b, int c)
{
    e[cnt] = b; w[cnt] = c; ne[cnt] = h[a]; h[a] = cnt++;
}

void insert(int x)
{
    int p = 0;
    for(int i = 30; i >= 0; i--)
    {
        int &s = son[p][x >> i & 1];
        if(!s)  s = ++idx;
        p = s;
    }
}

int query(int x)
{
    int p = 0, res = 0;
    for(int i = 30; i >= 0; i--)
    {
        int s = x >> i & 1;
        if(son[p][!s])
        {
            res += 1 << i;
            p = son[p][!s];
        }
        else p = son[p][s];
    }
    
    return res;
}

void dfs(int u, int fa, int sum)
{
    a[u] = sum;
    for(int i = h[u]; ~i; i = ne[i])
    {
        int j = e[i];
        if(j != fa) dfs(j, u, sum ^ w[i]);
    }
}

int main()
{
    scanf("%d", &n);
    memset(h, -1, sizeof h);
    for(int i = 0; i < n - 1; i++)
    {
        int a, b, c;
        cin >> a >> b >> c;
        add(a, b, c);
        add(b, a, c);
    }
    
    dfs(0, -1, 0);
    
    for(int i = 0; i < n; i++) insert(a[i]);
    
    int res = 0;
    for(int i = 0; i < n; i++) res = max(res, query(a[i]));
    
    cout << res <<endl;
    
}


你可能感兴趣的:(#,前缀树,算法,数据结构,dfs)