【字典树】小练

此小练都是一些裸的字典树的小练,主要是熟练字典树的运用;

题目参考自其他博客,在此就不一一贴出了;

字典树模板:点击打开链接



/****************************************字典树****************************************/

uva1401 : remember the word  (dp+字典树)
题意:给一个长串,若干短串,问长串可以被短串构成的种数,短串可重复使用;

思路:dp+字典树,转移方程:dp[i]=sum{dp[i]+len(x)|x是i~len前缀},具体看刘汝佳大白书209页;

转载 http://www.cnblogs.com/WABoss/p/5163511.html

代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

#define NODE 1000006
#define mod 20071027

int nex[NODE][26];
int v[NODE];
int node;
int dp[NODE];

void init()
{
    node=1;
    memset(nex[0],0,sizeof(nex[0]));
    memset(dp,0,sizeof(dp));
}

int add(char *str)
{
    int cur=0,k;
    int len=strlen(str);
    for(int i=0; i<len; i++)
    {
        k=str[i]-'a';
        if(!nex[cur][k])
        {
            memset(nex[node],0,sizeof(nex[node]));
            v[node]=0; //清空操作;
            nex[cur][k]=node++;
        }
        cur=nex[cur][k];
    }
    v[cur]++;
}

int main()
{
    int n;
    char S[300005];
    char s[110];

    int cas=0;
    while(scanf("%s",S)!=EOF)
    {
        init();

        scanf("%d",&n);
        while(n--)
        {
            scanf("%s",s);
            add(s);
        }

        int len=strlen(S);
        dp[len]=1;
        for(int i=len-1; i>=0; i--)
        {
            int u=0,x;
            for(int j=i; j<len; j++)
            {
                x=S[j]-'a';
                if(nex[u][x]==0)
                    break;      //
                u=nex[u][x];    //
                if(v[u])
                    dp[i]=(dp[i]+dp[j+1])%mod;
            }
        }

        printf("Case %d: %d\n",++cas,dp[0]);
    }
    return 0;
}

hdu1521:统计难题

题意:给定一个单词表的集合,然后给定询问串,问有多少个以询问串为前缀的单词;

代码:

#include<cstdio>
#include<cstring>

#define NODE 1000005

int next[NODE][26];
int v[NODE];
int node;

void init()  //初始化;
{
    node=1;  //节点个数;
    memset(next[0],0,sizeof(next[0]));
}

void add(char *str)  //加入
{
    int cur=0,k;
    int len=strlen(str);
    for(int i=0;i<len;i++)
    {
        k=str[i]-'a';
        if(next[cur][k]==0)
        {
            memset(next[node],0,sizeof(node));
            v[node]=0;  //清空操作;
            next[cur][k]=node++;
        }
        cur=next[cur][k];
        v[cur]++;
    }
}

int cal(char *str)  //查询
{
    int cur=0,k;
    int len=strlen(str);
    for(int i=0;i<len;i++)
    {
        k=str[i]-'a';
        if(next[cur][k])
            cur=next[cur][k];
        else
            return 0;
    }
    return v[cur];
}

int main()
{
    char str[20];
    init();
    while(1)
    {
        gets(str);
        if(str[0]=='\0')
            break;
        add(str);
    }

    while(scanf("%s",str)!=EOF)
    {
        printf("%d\n",cal(str));
    }
    return 0;
}

hdu1671:Phone List

题意:判断所给字串中是否有的字符串是其他字符串的前缀,有则输出NO;
思路:对于所有字符串先询问再插入,开标记数组标记字符串的末尾字符编号;
两种情况:1.寻找自己的过程中遇到完整的字符串,说明有字符串是他的前缀;
                 2.把自己找完了,发现最后一个字符还与其他字符相连,说明自己是其他字符串的前缀;

代码:

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;

#define NODE 1000006

int nex[NODE][10];
int v[NODE],biao[NODE];
int node;
int flag;

void init()
{
    node=1;
    memset(nex[0],0,sizeof(nex[0]));
//    memset(biao,0,sizeof(biao));
}

void add(char *str)
{
    int cur=0,k;
    int len=strlen(str);

    for(int i=0; i<len; i++)
    {
        k=str[i]-'0';
        if(!nex[cur][k])
        {
            memset(nex[node],0,sizeof(nex[node]));
            v[node]=0,biao[node]=0; //清空操作;
            nex[cur][k]=node++;
        }
        cur=nex[cur][k];
        v[cur]++;
    }
    biao[cur]=1;
}

void cal(char *str)
{
    int cur=0,k;
    int len=strlen(str);

    for(int i=0; i<len; i++)
    {
        k=str[i]-'0';
        if(nex[cur][k])
        {
            cur=nex[cur][k];
            if(biao[cur]) flag=1;
        }
        else
            return;
    }
    if(v[cur]>=1) flag=1;
}

int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        init();

        int n;
        scanf("%d",&n);

        flag=0;
        char s[20];
        for(int i=1; i<=n; i++)
        {
            scanf("%s",s);
            cal(s);
            add(s);
        }

        if(flag)
            puts("NO");
        else
            puts("YES");
    }
    return 0;
}

uva11732 "strcmp()" Anyone?

题意:比较任意两个字符串,如果两个字符串相同则比较了(长度+1)*2次即(公共前缀*2+2)次,如果两个字符串不同则比较了(公共前缀*2+1)次;

思路:利用字典树,不过如果用儿子节点表示法的话,内存可能超限(试过了不超),所以使用左儿子右兄弟的存储方法,利用此方法构成的是一棵二叉树。我们每要插入一字符串就会与之前所有插过的串相比。所有的串至少是要比一次的,如果串相同那就多比了一次,其他的就是:计算各字符的贡献!最后答案是ans+n*(n-1)/2;

代码:

#include<bits/stdc++.h>
#define N 4100000
#define ll long long

struct node
{
    char c;
    int l,r;
    int val;  //经过节点次数
    int cut;  //以节点为尾节点的次数
}trie[N];
int node;
ll ans=0;

void init()
{
    node=1;
    memset(trie,0,sizeof(trie));
}

void insert1(char *s)
{
    int rt,i;
    for(rt=0;*s;s++,rt=i)
    {
        for(i=trie[rt].l;i;i=trie[i].r)
        {
            if(trie[i].c==*s)
                break;
        }
        ans+=trie[i].val<<1;

        if(i==0)
        {
            trie[node].r=trie[rt].l;
            trie[node].l=0;
            trie[node].c=*s;
            trie[rt].l=node;
            i=node++;
        }
        trie[i].val++;
    }

    ans+=trie[rt].cut;
    trie[rt].cut++;
}

int main()
{
    int n,cas=1;
    char s[1100];
    while(scanf("%d",&n)!=EOF)
    {
        if(n==0) break;

        ans=0;
        init();
        for(int i=0;i<n;i++)
        {
            scanf("%s",s);
            insert1(s);
//            printf("%d\n",ans);
        }

        printf("Case %d: %lld\n",cas++,ans+n*(n-1)/2);
    }
    return 0;
}

/*
2
a
b
4
mat
hat
sir
fat
2
nyoj
nyist
*/

耗内存的儿子节点表示法:

#include<bits/stdc++.h>
#define ll long long
#define N 4100000

int nex[N][80];
int val[N],cut[N];
int node;

void init()
{
    memset(nex[0],0,sizeof(nex[0]));
    node=1;
}

ll ans=0;

void add(char *s,int T)
{
    int cur=0,k;
    int len=strlen(s);
    for(int i=0; i<len; i++)
    {
        k=s[i]-'0';
        if(!nex[cur][k])
        {
            memset(nex[node],0,sizeof(nex[node]));
            val[node]=0,cut[node]=0;

            nex[cur][k]=node++;
        }
        cur=nex[cur][k];

        ans+=val[cur]<<1;

        val[cur]++;
    }

    ans+=cut[cur];
    cut[cur]++;

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

int main()
{
    int n,cas=1;
    while(scanf("%d",&n)&&n)
    {
        init();
        char s[1100];

        ans=0;
        for(int i=1; i<=n; i++)
        {
            scanf("%s",s);
            add(s,i);
        }

        printf("Case %d: %lld\n",cas++,ans+n*(n-1)/2);
    }
    return 0;
}


/****************************************01字典树****************************************/

题目类型:两两异或求异或所得最大值、连续子序列异或最大值

hdu5269 ZYB loves Xor I

参考:http://blog.csdn.net/chy20142109/article/details/50704324

题意:输入一个含n个元素的序列A,求所有lowbit(Ai xor Aj)   ( i,j∈[1,n]) 的和;

思路:先考虑 lowbit ( ) 的性质:lowbit等于从低位儿开始第一个不等于0的位置对应的值,比如lowbit(13)=1,13=1101,第一个不等于0的就是第一位儿。那么我们就

是统计它一路到底与它的同位儿相异的个数*该位儿的值(贡献)

这一题存的01串要从低位儿开始存,样例模拟一下:10 12 4

代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;
#define por(i,j,k) for(int i=j;i<=k;i++)
#define nor(i,j,k) for(int i=j;i>=k;i--)
#define clean(x,y) memset(x,y,sizeof(x))
#define ll long long
#define mod 998244353
#define N 1550000

int nex[N][2];
int val[N];
int node;

void init()
{
    node=1;
    clean(nex[0],0);
    val[0]=0;
}

void add(int c)
{
    int cur=0,k;
    por(i,0,29)
    {
        k=c&1;
        if(!nex[cur][k])
        {
            clean(nex[node],0);
            val[node]=0;

            nex[cur][k]=node++;
        }
        cur=nex[cur][k];
        val[cur]++;
        c>>=1;
    }
}

ll cal(int c)
{
    int cur=0,k;
    ll ans=0,value=1;
    por(i,0,29)
    {
        k=c&1;
        ans+=val[nex[cur][1-k]]*value;
        cur=nex[cur][k];
        value<<=1;
        c>>=1;
    }
    return ans;
}

int main()
{
    int t,cas=0;
    scanf("%d",&t);
    while(t--)
    {
        int n;
        ll ans=0;

        init();

        scanf("%d",&n);
        por(i,1,n)
        {
            int x;
            scanf("%d",&x);
            add(x);
            ans+=cal(x);
//cout<<ans<<endl;
            if(ans>=mod) ans%=mod;
        }
        printf("Case #%d: ",++cas);
        printf("%I64d\n",ans*2%mod);
    }
    return 0;
}

hdu 5536 Chip Factory

题意:输入长为n的序列,计算 maxi,j,k(si+sj)sk,其中i , j , k各不相同;

思路:先把序列的所有元素存到字典树中,然后枚举i,j 删除si,sj,查询完之后再加进去;

求与x异或后的最大值找到与其二进制不同的最多的那个数来异或;
//学习到了思路,枚举删除的思路
//学习到了字典树的标记;:
1.标记经过某个节点的次数;
2.标记某个节点作为结尾的次数;
3.本题就是每个数都是按照30位二进制插入,到最后第三十层每一个数都有自己的结尾节点,在结尾节点保存的就是这个数的十进制值!

代码:

#include<iostream>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;
#define por(i,j,k) for(int i=j;i<=k;i++)
#define nor(i,j,k) for(int i=j;i>=k;i--)
#define clean(x,y) memset(x,y,sizeof(x))
#define ll long long
#define mod 998244353
#define N 50005



int nex[N][2];
int num[N],val[N];
int a[N];
int node;

void init()
{
    node=1;
    clean(nex,0);
    clean(num,0);
    clean(val,0);
}

void add(int c)
{
    int cur=0,k;
    nor(i,30,0)
    {
        k=(c>>i)&1;
        if(!nex[cur][k])
            nex[cur][k]=node++;

        cur=nex[cur][k];
        num[cur]++;
    }
    val[cur]=c;
}

ll cal(int c)
{
    int cur=0,k;
    nor(i,30,0)
    {
        k=(c>>i)&1;
        if(nex[cur][1-k]&&num[nex[cur][1-k]])
            cur=nex[cur][1-k];
        else
            cur=nex[cur][k];
    }
    return c^val[cur];
}

void del(int c,int d)
{
    int cur=0,k;
    nor(i,30,0)
    {
        k=(c>>i)&1;

        cur=nex[cur][k];
        num[cur]+=d;
    }
}

int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        init();

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

        ll maxx=0;
        for(int i=1;i<n;i++)
        {
            del(a[i],-1);
            for(int j=i+1;j<=n;j++)
            {
                del(a[j],-1);
                maxx=max(maxx,cal(a[i]+a[j]));
                del(a[j],1);
            }
            del(a[i],1);
        }

        printf("%I64d\n",maxx);

    }
    return 0;
}

hdu 4825 Xor Sum

题意:给定一个n元素的集合,m次询问每次给定一个数,问与集合中那个数异或值最大并输出那个数字;

思路:

代码:

#include<iostream>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;
#define por(i,j,k) for(int i=j;i<=k;i++)
#define nor(i,j,k) for(int i=j;i>=k;i--)
#define clean(x,y) memset(x,y,sizeof(x))
#define ll long long
#define mod 998244353
#define N 5000000

int nex[N][2];
int num[N];
int val[N];
int node;

void init()
{
    node=1;
    clean(nex,0);
    clean(num,0);
    clean(val,0);
}

void add(int c)
{
    int cur=0,k;
    nor(i,31,0)
    {
        k=(c>>i)&1;
        if(!nex[cur][k])
            nex[cur][k]=node++;
        cur=nex[cur][k];
        num[cur]++;
    }
    val[cur]=c;
}

int cal(int c)
{
    int cur=0,k;
    nor(i,31,0)
    {
        k=(c>>i)&1;
        if(nex[cur][1-k]&&num[nex[cur][1-k]])
            cur=nex[cur][1-k];
        else
            cur=nex[cur][k];
    }
    return val[cur]; //
}

int main()
{
    int t,cas=0;
    scanf("%d",&t);
    while(t--)
    {
        init();

        int n,m;
        scanf("%d%d",&n,&m);
        por(i,1,n)
        {
            int x;
            scanf("%d",&x);
            add(x);
        }

        printf("Case #%d:\n",++cas);
        por(i,1,m)
        {
            int x;
            scanf("%d",&x);
            printf("%d\n",cal(x));
        }
    }
    return 0;
}

④poj 3764 The xor-longest Path

题意:给定一棵数,每条边都有边权,一条路径的异或长度就是这条路径上所有边的异或值

从三号节点到7号节点也是一条路径~

思路:异或性质 x^y = (0^x) ^ (0^y) ;以0点为源点,保存各节点到源点这条路径的异或长度值到a[]。这个问题就转化为求a数组两两异或值的最大值!

代码:

#include<iostream>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;
#define por(i,j,k) for(int i=j;i<=k;i++)
#define nor(i,j,k) for(int i=j;i>=k;i--)
#define clean(x,y) memset(x,y,sizeof(x))
#define ll long long
#define mod 998244353
#define N 3005000

int ch[N][2];
int val[N];
int node,cnt;

int head[100010],nod[100010];
struct info
{
    int to,v;
    int nex;
} edge[100010];

void add_edge(int from,int to,int val)
{
    edge[++cnt].to=to;
    edge[cnt].v=val;
    edge[cnt].nex=head[from];
    head[from]=cnt;
}

void init()
{
    cnt=0;
    node=1;
    clean(ch[0],0);

    clean(edge,0);
    clean(nod,0);
    clean(head,0);
}

void add(int c)
{
    int cur=0,k;
    nor(i,30,0)
    {
        k=(c>>i)&1;
        if(!ch[cur][k])
        {
            clean(ch[node],0);
            val[node]=0;

            ch[cur][k]=node++;
        }
        cur=ch[cur][k];
    }
    val[cur]=c;
}

ll cal(int c)
{
    int cur=0,k;
    nor(i,30,0)
    {
        k=(c>>i)&1;
        if(ch[cur][1-k])
            cur=ch[cur][1-k];
        else
            cur=ch[cur][k];
    }
    return val[cur]^c;
}

int main()
{
    int n;
    while(scanf("%d",&n)!=EOF)
    {
        init();

        por(i,1,n-1)
        {
            int a,b,c;
            scanf("%d%d%d",&a,&b,&c);
            add_edge(min(a,b),max(a,b),c);
        }

        for(int i=0; i<n; i++)
            for(int j=head[i]; j; j=edge[j].nex)
                nod[edge[j].to]=nod[i]^edge[j].v;

        ll maxx=0;
        por(i,1,n)
        {
            add(nod[i]);
            maxx=max(maxx,cal(nod[i]));
        }
        printf("%I64d\n",maxx);
    }
    return 0;
}

bzoj 4260 Codechef REBXOR

题意:输入有n个元素的序列,从序列中找到两个不想交的区间,求两个区间的异或值的和的最大值;

思路:分别序列的前缀异或和与后缀异或和;然后枚举序列中元素计算  【1~i 的异或和   +   (i+1)~n的异或和】

代码:

#include<iostream>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>


using namespace std;
#define por(i,j,k) for(int i=j;i<=k;i++)
#define nor(i,j,k) for(int i=j;i>=k;i--)
#define clean(x,y) memset(x,y,sizeof(x))
#define ll long long
#define mod 998244353
#define N 15000000


int nex[N][2];
int val[N];
int a[500005],pre[500005],suf[500005];
ll dp[500005];
int node;


void init()
{
    clean(nex[0],0);
    node=1;
}


void add(int c)
{
    int cur=0,k;
    nor(i,30,0)
    {
        k=(c>>i)&1;
        if(!nex[cur][k])
        {
            clean(nex[node],0);
            val[node]=0;


            nex[cur][k]=node++;
        }
        cur=nex[cur][k];
    }
    val[cur]=c;
}


ll cal(int c)
{
    int cur=0,k;
    nor(i,30,0)
    {
        k=(c>>i)&1;
        if(nex[cur][1-k])
            cur=nex[cur][1-k];
        else
            cur=nex[cur][k];
    }
    return val[cur]^c;
}


int main()
{
    int n;
    while(scanf("%d",&n)!=EOF)
    {
        init();
        pre[0]=suf[n+1]=0;
        por(i,1,n) scanf("%d",&a[i]);
        por(i,1,n) pre[i]=pre[i-1]^a[i];
        nor(i,n,1) suf[i]=suf[i+1]^a[i];


        clean(dp,0);
        add(pre[0]);
        por(i,1,n)
        {
            dp[i]=max(dp[i-1],cal(pre[i]));
            add(pre[i]);
        }


        ll maxx=0;
        init();
        add(suf[n+1]);
        nor(i,n,1)
        {
            maxx=max(maxx,dp[i-1]+cal(suf[i]));
            add(suf[i]);
        }


        printf("%lld\n",maxx);
    }
    return 0;
}

usaco training 6.1.3 Cow XOR

题意:输入一个含n个元素的序列,求子序列异或值最大值是多少,并且输出此子序列的开头结尾下标,如果子序列的值一样,选择结尾下标较小的,还是相同?选择较短的那条序列!

思路:利用前缀异或值数组;计算出连续子序列异或值最大是多少,因为是按序输入所以不用处理结尾下标较小,如果有两个前缀值相同,则后来的回更新前面已经存在的,这会保证选择的是较短的那条序列;

代码:

#include<iostream>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;
#define por(i,j,k) for(int i=j;i<=k;i++)
#define nor(i,j,k) for(int i=j;i>=k;i--)
#define clean(x,y) memset(x,y,sizeof(x))
#define ll long long
#define mod 998244353
#define N 2005000

int nex[N][2];
struct info
{
    int v,id;
}val[N];
int node;
int pre[100100];

void init()
{
    node=1;
    clean(nex[0],0);

    clean(pre,0);
}

void add(int c,int idx)
{
    int cur=0,k;
    nor(i,20,0)
    {
        k=(c>>i)&1;
        if(!nex[cur][k])
        {
            clean(nex[node],0);
            val[node].id=0,val[node].v=0;

            nex[cur][k]=node++;
        }
        cur=nex[cur][k];
    }
    val[cur].id=idx;
    val[cur].v=c;
}

info cal(int c)
{
    int cur=0,k;
    nor(i,20,0)
    {
        k=(c>>i)&1;
        if(nex[cur][1-k])
            cur=nex[cur][1-k];
        else
            cur=nex[cur][k];
    }

    return val[cur];
}

int main()
{
    int n;
    while(scanf("%d",&n)!=EOF)
    {
        init();
        por(i,1,n)
        {
            int x;
            scanf("%d",&x);
            pre[i]=pre[i-1]^x;
        }

//        por(i,1,n)
//        cout<<pre[i]<<" ";
//        cout<<endl;

        int ans=0,ans_l=0,ans_r=0;
        info an;
        add(pre[0],0);
        por(i,1,n)
        {
            add(pre[i],i);
            an=cal(pre[i]),an.v^=pre[i];

            if(an.v>ans)  //选最大的
            {
                ans=an.v;
                ans_l=(an.id+1);
                ans_r=i;
//                printf("%d : ans:%d l:%d\n",i,an.v,an.id);
            }
        }
        printf("%d %d %d\n",ans,ans_l,ans_r);
    }
    return 0;
}
就是因为这一题才开始学了字典树;01二进制思想的运用:01字典树。

学习时间有点长了,近一周

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值