01字典树 刷题记录

每日推荐–详解主席树
题目列表1
题目列表2
题目3
1 HDU 1251

统计难题
Time Limit: 4000/2000 MS (Java/Others) Memory Limit: 131070/65535 K (Java/Others)
Total Submission(s): 52206 Accepted Submission(s): 18295

Problem Description
Ignatius最近遇到一个难题,老师交给他很多单词(只有小写字母组成,不会有重复的单词出现),现在老师要他统计出以某个字符串为前缀的单词数量(单词本身也是自己的前缀).

Input
输入数据的第一部分是一张单词表,每行一个单词,单词的长度不超过10,它们代表的是老师交给Ignatius统计的单词,一个空行代表单词表的结束.第二部分是一连串的提问,每行一个提问,每个提问都是一个字符串.

注意:本题只有一组测试数据,处理到文件结束.

Output
对于每个提问,给出以该字符串为前缀的单词的数量.

Sample Input
banana
band
bee
absolute
acm

ba
b
band
abc

Sample Output
2
3
1
0

Author
Ignatius.L

Recommend
Ignatius.L

扫描二维码关注公众号,回复: 2407438 查看本文章
#include <iostream>
#include<bits/stdc++.h>
using namespace std;
char word[200];
const int maxn = 1e6+7;
int trie[maxn][30],num[maxn],cnt = 1;
void Trie_insert(char* word)
{
    int p = 0;
    for(int i=0;word[i];i++)
    {
        if(trie[p][word[i]-'a']==0)
            trie[p][word[i]-'a'] = cnt++;
        p = trie[p][word[i]-'a'];
        num[p]++;
    }
}
int Trie_search(char* word)
{
    int p = 0;
    for(int i=0;word[i];i++)
    {
        int  t = word[i]-'a';
        if(trie[p][t]==0)return 0;
        p = trie[p][t];
    }
    return num[p];
}
int main()
{
    //freopen("in.txt","r",stdin);
    while(gets(word))
    {
        if(word[0]==NULL)
        {
            break;
        }
        Trie_insert(word);
    }
    while(~scanf("%s",word))
    {
        printf("%d\n",Trie_search(word));
    }
    return 0;
}
// https://blog.csdn.net/williamsun0122/article/details/71056547

2 . 参考博客
2017HZAU现场赛H-MathematicalGame
题目链接:http://acm.hzau.edu.cn/problem.php?id=1206

题意:有T组样例,每组样例给n个数,a1…an(n<=1000000)。求这n个数中最大异或和值的区间。有多个答案区间按字典序输出。

题解:把1-n的所有前缀异或和插入01字典树,然后按区间异或的性质扫一遍就可以了。注意一下区间按字典序输出。
注意数组大小,会卡初始化的常数

#include <iostream>
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e6+7;
int trie[maxn*32][2];
int idx[maxn*32],cnt =0;
int ans,l,r;
void Trie_insert(int u,int x)
{
    int p = 0;
    for(int i=31; i>=0; i--)
    {
        int t = (x>>i)&1;
        if(!trie[p][t])trie[p][t] = cnt++;
        p = trie[p][t];
    }
    if(u<idx[p])idx[p] = u;//p 为记录下前缀和的右区间,即答案的左区间
}
void init()
{
    memset(trie,0,sizeof trie);
    cnt = 1;
    ans =0 ;
    l = 0;
    r =0 ;
    memset(idx,0x3f3f3f3f,sizeof idx);
}
void Trie_query(int u,int x)
{
    int p = 0;
    int m = 0;
    for(int i=31; i>=0; i--)
    {
        int t = (x>>i)&1;
        if(trie[p][t^1])
        {
            p = trie[p][t^1];
            m+=(1<<i);
        }
        else
        {
            p = trie[p][t];
            m+=(0<<i);
        }
    }
    if(m>ans)
    {
        l = idx[p];
        r = u;
        ans = m;
    }
    else if(m==ans)
    {
        ans = m;
        if(idx[p]<l)
        {
            l = idx[p];
            r = u;
        }
        else if(idx[p]==l&&u<r)
        {
            r = u;
        }
    }
}
int main()
{
    //freopen("in.txt","r",stdin);
    int T,n,tmp;
    scanf("%d",&T);
    int id = 0;
    while(T--)
    {
        int y = 0;
        scanf("%d",&n);
        init();
        Trie_insert(0,0);
        for(int i=1; i<=n; i++)
        {
            scanf("%d",&tmp);
            y^=tmp;
            Trie_insert(i,y);
            Trie_query(i,y);
        }
        printf("Case #%d:\n%d %d\n",++id,l+1,r);
    }
    return 0;
}

3。Xor Sum HDU - 4825
01字典树裸题
Zeus 和 Prometheus 做了一个游戏,Prometheus 给 Zeus 一个集合,集合中包含了N个正整数,随后 Prometheus 将向 Zeus 发起M次询问,每次询问中包含一个正整数 S ,之后 Zeus 需要在集合当中找出一个正整数 K ,使得 K 与 S 的异或结果最大。Prometheus 为了让 Zeus 看到人类的伟大,随即同意 Zeus 可以向人类求助。你能证明人类的智慧么?
Input
输入包含若干组测试数据,每组测试数据包含若干行。
输入的第一行是一个整数T(T < 10),表示共有T组数据。
每组数据的第一行输入两个正整数N,M(<1=N,M<=100000),接下来一行,包含N个正整数,代表 Zeus 的获得的集合,之后M行,每行一个正整数S,代表 Prometheus 询问的正整数。所有正整数均不超过2^32。
Output
对于每组数据,首先需要输出单独一行”Case #?:”,其中问号处应填入当前的数据组数,组数从1开始计算。
对于每个询问,输出一个正整数K,使得K与S异或值最大。
Sample Input
2
3 2
3 4 5
1
5
4 1
4 6 5 6
3
Sample Output
Case #1:
4
3
Case #2:
4

#include <iostream>
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e5+7;
int trie[maxn*32][2];
int idx[maxn*32],cnt =0;
int ans,l,r;
void Trie_insert(int x)
{
    int p = 0;
    for(int i=31; i>=0; i--)
    {
        int t = (x>>i)&1;
        if(!trie[p][t])trie[p][t] = cnt++;
        p = trie[p][t];
    }
    idx[p] = x;
}
void init()
{
    memset(trie,0,sizeof trie);
    cnt = 1;
    ans =0 ;
    l = 0;
    r =0 ;
    memset(idx,0x3f3f3f3f,sizeof idx);
}
int  Trie_query(int x)
{
    int p = 0;
    int m = 0;
    for(int i=31; i>=0; i--)
    {
        int t = (x>>i)&1;
        if(trie[p][t^1])
        {
            p = trie[p][t^1];
            m+=(1<<i);
        }
        else
        {
            p = trie[p][t];
            m+=(0<<i);
        }
    }
     return idx[p];
}
int main()
{
    //freopen("in.txt","r",stdin);
    int T,n,tmp,m;
    scanf("%d",&T);
    int id = 0;
    while(T--)
    {

        scanf("%d%d",&n,&m);
        init();

        for(int i=1; i<=n; i++)
        {
            scanf("%d",&tmp);
            Trie_insert(tmp);

        }
        printf("Case #%d:\n",++id);
        for(int i=1;i<=m;i++)
        {
            scanf("%d",&tmp);
            int ans = Trie_query(tmp);
            printf("%d\n",ans);

        }


    }
    return 0;
}

4。HDU - 5536
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5536

题意:有一个数组a[], 包含n个数,从n个数中找到三个数使得 (a[i]+a[j])⊕a[k]最大,i,j,k不同;

求异或的结果最大所以我们可以用01字典树,先把所有的数加入字典树中,从n个数中选出两个数a[i]和a[j],

先把他们从字典树中删除,然后找到与a[i]+a[j]异或最大的数,和结果取最大值即可;

最后不要忘记再把a[i]和a[j]添加到字典树中即可;

#include <iostream>
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e3+7;
int trie[maxn*32][2],a[maxn];
int idx[maxn*32],num[maxn*32],cnt =0;
int ans,l,r;
void Trie_insert(int x,int op)
{
    int p = 0;
    for(int i=31; i>=0; i--)
    {
        int t = (x>>i)&1;
        if(!trie[p][t])trie[p][t] = cnt++;
        p = trie[p][t];
        num[p]+=op;
    }
    idx[p] = x;
}
void init()
{
    memset(num,0,sizeof num);
    memset(trie,0,sizeof trie);
    cnt = 1;
    ans =0 ;
    l = 0;
    r =0 ;
    memset(idx,0x3f3f3f3f,sizeof idx);
}
int Trie_query(int x)
{
    int p = 0;
    int m = 0;
    for(int i=31; i>=0; i--)
    {
        int t = (x>>i)&1;
        if(trie[p][t^1]&&num[trie[p][t^1]]>0)
        {
            p = trie[p][t^1];
        }
        else
        {
            p = trie[p][t];
        }
    }
    return idx[p]^x;
}
int main()
{
    //freopen("in.txt","r",stdin);
    int T,n,tmp,m;
    scanf("%d",&T);
    while(T--)
    {

        scanf("%d",&n);
        init();
        for(int i=1; i<=n; i++)
        {
            scanf("%d",&a[i]);
            Trie_insert(a[i],1);
        }
        int yy = 0;
        for(int i=1; i<=n; i++)
        {
            Trie_insert(a[i],-1);
            for(int j=1; j<=n; j++)
            {
                if(i==j)continue;
                Trie_insert(a[j],-1);
                ans = Trie_query(a[i]+a[j]);
                yy = max(yy,ans);
                Trie_insert(a[j],1);
            }
            Trie_insert(a[i],1);
        }
        printf("%d\n",yy);
    }
    return 0;
}

5。
bzoj 4260
题目传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=4260
让你在长为n的数组里找两个不相交的连续区间,使得两个区间分别的异或和求和之后最大
反正看到连续区间异或和最大,我只会一个套路,就是01字典树了,先正着来一遍前缀异或和,同时DP表示到i为止,前面的区间异或和最大是多少,然后倒着来一遍后缀异或和.

#include <iostream>
#include<bits/stdc++.h>
using namespace std;
const int maxn = 4e5+7;
int trie[maxn*32][2],a[maxn],dp[maxn];
int idx[maxn*32],num[maxn*32],cnt =0;
int pre[maxn],suff[maxn];
int ans,l,r;
void Trie_insert(int x)
{
    int p = 0;
    for(int i=31; i>=0; i--)
    {
        int t = (x>>i)&1;
        if(!trie[p][t])trie[p][t] = cnt++;
        p = trie[p][t];

    }
    idx[p] = x;
}
void init()
{
    memset(num,0,sizeof num);
    memset(trie,0,sizeof trie);
    cnt = 1;
}
int Trie_query(int x)
{
    int p = 0;
    int m = 0;
    for(int i=31; i>=0; i--)
    {
        int t = (x>>i)&1;
        if(trie[p][t^1])
        {
            p = trie[p][t^1];
        }
        else
        {
            p = trie[p][t];
        }
    }
    return idx[p]^x;
}
int main()
{
    //freopen("in.txt","r",stdin);
    int T,n,tmp,m;
    scanf("%d",&n);
    pre[0] = suff[n+1] = 0;
    init();
    for(int i=1; i<=n; i++)
    {
        scanf("%d",&a[i]);
        pre[i] = pre[i-1]^a[i];
    }
    for(int i=n; i>=1; i--)
        suff[i] = suff[i+1]^a[i];
    memset(dp,0,sizeof dp);
    Trie_insert(0);
    for(int i=1;i<=n;i++)
    {
        dp[i] = max(dp[i-1],Trie_query(pre[i]));
        Trie_insert(pre[i]);
    }
     init();
     Trie_insert(0);
     int ans =0;
     for(int i=n;i>=1;i--)
     {
         ans = max(ans,Trie_query(suff[i])+dp[i-1]);
         Trie_insert(suff[i]);
     }
     cout<<ans<<endl;

    return 0;
}

6。POJ3764
题目传送门:http://poj.org/problem?id=3764
这题是树上的最大异或和路径,但是其实也是一样的套路,在dfs的时候,把从根到当前节点的异或和,去01字典树里查询,找到一条路径和当前路径异或和最大,这找到的绝对是两条相连的,因为你往01字典树里扔的,就是从根到当前节点的异或和

#include <iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cstdlib>
using namespace std;
const int maxn = 1e5+7;
int trie[maxn*32][2],a[maxn];
int idx[maxn*32],num[maxn*32],cnt =0;
int ans,l,r;
int tot = 0;
int head[maxn];
struct node
{
    int v,next,c;
}edge[maxn*2];
void addedge(int u,int v,int w)
{
    edge[tot].v= v;
    edge[tot].c = w;
    edge[tot].next = head[u];
    head[u] = tot++;
}
void Trie_insert(int x)
{
    int p = 0;
    for(int i=31; i>=0; i--)
    {
        int t = (x>>i)&1;
        if(!trie[p][t])trie[p][t] = cnt++;
        p = trie[p][t];

    }
    idx[p] = x;
}
void init()
{
    memset(num,0,sizeof num);
    memset(head,-1,sizeof head);
    tot = 1;
    memset(trie,0,sizeof trie);
    cnt = 1;
    ans = 0;
}
int Trie_query(int x)
{
    int p = 0;
    int m = 0;
    for(int i=31; i>=0; i--)
    {
        int t = (x>>i)&1;
        if(trie[p][t^1])
        {
            p = trie[p][t^1];
        }
        else
        {
            p = trie[p][t];
        }
    }
    return idx[p]^x;
}
void dfs(int u,int fa,int c)
{
    Trie_insert(c);
    for(int i=head[u];~i;i=edge[i].next)
    {
        int v =edge[i].v;
        if(v==fa)continue;
        ans = max(ans,Trie_query(c^edge[i].c));
        dfs(v,u,c^edge[i].c);
    }
}
int main()
{
    //freopen("in.txt","r",stdin);
    int T,n,tmp,m,a,b,c;
    while(cin>>n)
    {
        init();
        for(int i=1;i<n;i++)
        {
            scanf("%d%d%d",&a,&b,&c);
            addedge(a,b,c);
            addedge(b,a,c);
        }
        dfs(0,-1,0);
        cout<<ans<<endl;
    }




    return 0;
}

7。CSU-1216: 异或最大值-trie-01字典树

http://acm.csu.edu.cn/OnlineJudge/problem.php?id=1216

给定一些数,求这些数中两个数的异或值最大的那个值

任意两数最大异或值

这个问题可以用01-字典树很好地解决

即把所有数先按二进制从高到低位看成字符串插入trie。

枚举每个数,作为X,然后去trie里尽可能找每一位与X的二进制位相反的数,不断更新答案

#include <iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cstdlib>
using namespace std;
const int maxn = 1e5+7;
int trie[maxn*32][2],a[maxn];
int idx[maxn*32],num[maxn*32],cnt =0;
int ans,l,r;
int tot = 0;
void Trie_insert(int x)
{
    int p = 0;
    for(int i=31; i>=0; i--)
    {
        int t = (x>>i)&1;
        if(!trie[p][t])trie[p][t] = cnt++;
        p = trie[p][t];

    }
    idx[p] = x;
}
void init()
{
    memset(num,0,sizeof num);

    tot = 1;
    memset(trie,0,sizeof trie);
    cnt = 1;
    ans = 0;
}
int Trie_query(int x)
{
    int p = 0;
    int m = 0;
    for(int i=31; i>=0; i--)
    {
        int t = (x>>i)&1;
        if(trie[p][t^1])
        {
            p = trie[p][t^1];
        }
        else
        {
            p = trie[p][t];
        }
    }
    return idx[p]^x;
}

int main()
{
    //freopen("in.txt","r",stdin);
    int T,n,tmp,m,b,c;
    while(cin>>n)
    {
        init();

        for(int i=1;i<=n;i++){scanf("%d",&a[i]);
        Trie_insert(a[i]);
        }
        for(int i=1;i<=n;i++)
        {
            ans = max(ans,Trie_query(a[i]));

        }
        printf("%d\n",ans);

    }




    return 0;
}

猜你喜欢

转载自blog.csdn.net/axuhongbo/article/details/80776158