牛客题单——trie树、kmp、hash

题单链接

兔子与兔子

这道题就没啥好说的了,字符串哈希裸题

#include <bits/stdc++.h>
using namespace std;
typedef unsigned long long ll; 
const int N=1e6+10,B=13331;
int n;
char str[N];
ll p[N],h[N];
ll get_hash(int l,int r)
{
    return h[r]-h[l-1]*p[r-l+1];
}
int main()
{
    cin>>str+1;
    int n=strlen(str+1);
    p[0]=1;
    for(int i=1;i<=n;i++)
    {
        h[i]=h[i-1]*B+str[i]-'a'+1;
        p[i]=p[i-1]*B;
    }
    cin>>n;
    while(n--)
    {
        int x1,y1,x2,y2;
        cin>>x1>>y1>>x2>>y2;
        if(get_hash(x1,y1)==get_hash(x2,y2)) cout<<"Yes"<<endl;
        else cout<<"No"<<endl;
    }
    return 0;
}

后缀数组

这道题首先要给一堆后缀数组排序
排序算法本身是nlogn的复杂度,给两个字符串比较的复杂度是n
这样整体的复杂度就是n2logn的,这样的复杂度肯定是无法接受的

考虑用字符串哈希来给两个字符串比较优化时间复杂度

可以先计算出两个字符串最大的相等的前缀的长度,然后直接比较下一个字符的大小关系,其中找出两个字符串最大的相等的前缀的长度可以使用二分来解决,这样就只需要logn的复杂度了,整体的复杂度就可以过关了

#include <bits/stdc++.h>
using namespace std;
typedef unsigned long long ll;
const int N=3e5+10,B=13331;
int n;
char str[N];
int p[N],h[N];
int sa[N];
ll get_hash(int l,int r)
{
    return h[r]-h[l-1]*p[r-l+1];
}
int get_min(int a,int b) //计算两字符串最长的相等的前缀的长度
{
    int l=0,r=min(n-a+1,n-b+1);
    while(l<r)
    {
        int mid=(l+r+1)>>1;
        if(get_hash(a,a+mid-1)!=get_hash(b,b+mid-1)) r=mid-1;
        else l=mid;
    }
    return l;
}
bool cmp(int a,int b)
{
    int l=get_min(a,b);
    //防止数组越界
    int al=a+l>n?-1:str[a+l];
    int bl=b+l>n?-1:str[b+l];
    return al<bl;
}
int main()
{
    cin>>str+1;
    n=strlen(str+1);
    p[0]=1;
    for(int i=1;i<=n;i++)
    {
        h[i]=h[i-1]*B+str[i]-'a'+1;
        p[i]=p[i-1]*B;
        sa[i]=i;
    }
    sort(sa+1,sa+1+n,cmp);
    for(int i=1;i<=n;i++) cout<<sa[i]-1<<" ";
    cout<<endl;
    for(int i=1;i<=n;i++)
    {
        if(i==1) cout<<"0"<<" ";
        else cout<<get_min(sa[i-1],sa[i])<<" ";
    }
    cout<<endl;
    return 0;
}

Palindrome(回文子串的最大长度)

这道题是一个用字符串哈希的题
要找到回文子串就需要字符串左右两半是一样的
因此只需要将字符串正序逆序的哈希值全部预处理出来然后进行比较即可

首先对字符串进行预处理,在每个字符前面有加上一个一样的字符,这也就可以在枚举每个字符作为中点的时候就不用分奇偶进行讨论了

然后枚举每个字符作为中点的时候用一个二分来降低时间复杂度

#include <bits/stdc++.h>
using namespace std;
typedef unsigned int ll;
const int N=2*1e6+10,B=13331;
char str[N];
ll hl[N],hr[N]; //正序逆序点哈希值
ll p[N]; //哈希算法中每一位的权值
ll get_hash(ll h[],int l,int r) //计算哈希值
{
    return h[r]-h[l-1]*p[r-l+1];
}
int main()
{
    for(int k=1;scanf("%s",str+1)&&strcmp(str+1,"END");k++)
    {
        int n=strlen(str+1)*2;
        //cout<<n<<endl;
        for(int i=n;i>0;i-=2)
        {
            str[i]=str[i/2];
            str[i-1]='z'+1;
        }
        //cout<<str+1<<endl;
        p[0]=1; //初始化,放置下面运算过程中p数组都变成0
        for(int i=1,j=n;i<=n;i++,j--)
        {
            hl[i]=hl[i-1]*B+str[i]-'a'+1; //处理正序哈希值
            hr[i]=hr[i-1]*B+str[j]-'a'+1; //处理逆序哈希值
            p[i]=p[i-1]*B;
        }
        int res=0;
        for(int i=1;i<=n;i++)
        {
            int l=0,r=min(i-1,n-i);
            while(l<r)
            {//cout<<"***"<<endl;
                int mid=(l+r+1)>>1;
                if(get_hash(hl,i-mid,i-1)!=get_hash(hr,n-(i+mid)+1,n-(i+1)+1)) r=mid-1;
                else l=mid;
            }
            if(str[i-l]=='z'+1) res=max(res,l);
            else res=max(res,l+1);
        }
        printf("Case %d: %d\n",k,res);
    }
    return 0;
}

Period(周期)

这道题是一个用kmp算法的题,kmp算法不仅可以用来求解字符串匹配问题,还可以利用next数组中的信息求解一些循环节类的问题

kmp算法求解的是在一个字符串中以第 i 个字符为结尾的最长的前缀等于最长的后缀时的长度(详细的就不展开讲了)

接下来说说怎么用next数组中的信息求出循环节
在这里插入图片描述

设一个字符串1~n,next的位置是 i ,假设这是一个带循环节的字符串
那么就有上图中的四段分别相等
要得到4号串只需要 i -next[i]
求出循环次数只需要 i / ( i -next[i] ),即可,但是必须整除

#include <bits/stdc++.h>
using namespace std;
const int N=1e6+10;
int n;
char str[N];
int ne[N];
int main()
{
    for(int i=1;cin>>n&&n;i++)
    {
        cin>>str+1;
        for (int i = 2, j = 0; i <= n; i ++ )
        {
            while (j && str[i] != str[j + 1]) j = ne[j];
            if (str[i] == str[j + 1]) j ++ ;
            ne[i] = j;
        }
        cout<<"Test case #"<<i<<endl;
        for(int i=2;i<=n;i++)
        {
            int temp=i-ne[i];
            if(i>temp&&i%temp==0) cout<<i<<" "<<i%temp<<endl;
        }
    }
    return 0;
}

前缀统计

这道题就是一个很简单的trie树的题
先把建立索引的字符串存下来
然后在查询即可

#include <bits/stdc++.h>
using namespace std;
const int N=1e6+10;
int n,m;
int son[N][30],cnt[N],idx=1;
char str[N];
void insert()
{
    int p=0;
    for(int i=0;str[i];i++)
    {
        int s=str[i]-'a';
        if(!son[p][s]) son[p][s]=idx++;
        p=son[p][s];
    }
    cnt[p]++;
}
int query()
{
    int p=0,res=0;
    for(int i=0;str[i];i++)
    {
        int s=str[i]-'a';
        if(!son[p][s]) break;
        p=son[p][s];
        res+=cnt[p];
    }
    return res;
}
int main()
{
    cin>>n>>m;
    while(n--)
    {
        scanf("%s",str);
        insert();
    }
    while(m--)
    {
        scanf("%s",str);
        printf("%d\n",query());
    }
    return 0;
}

The XOR Largest Pair(最大异或对)

这道题是trie树的另一种应用
trie树不仅可以用来存储字符串,还可以用来存数

这道题中要求异或值最大的结果是多少,可以把一个数的二进制表示按照从高位到低位存储在trie树中

查询需要贪心的思想,越高的位不同,异或值越大
因此只需要在trie树上按照上述规则走到叶子节点即可

#include <bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int n;
int son[31*N][2],idx=1;
void insert(int x)
{
    int p=0;
    for(int i=30;i>=0;i--) //从最高位开始存
    {
        int u=x>>i&1;
        if(!son[p][u]) son[p][u]=idx++;
        p=son[p][u];
    }
}
int query(int x)
{
    int p=0,res=0;
    for(int i=30;i>=0;i--)
    {
        int u=x>>i&1;
        if(son[p][!u])
        {
            res=res*2+!u;
            p=son[p][!u];
        }
        else
        {
            res=res*2+u;
            p=son[p][u];
        }
    }
    return res;
}
int main()
{
    cin>>n;
    int res=-1;
    for(int i=0;i<n;i++)
    {
        int x;
        cin>>x;
        if(!i) insert(x); //处理边界
        else
        {
            int t=query(x);
            //cout<<"****"<<t<<endl;
            insert(x);
            res=max(res,t^x);
        }
    }
    cout<<res<<endl;
    return 0;
}

The xor-longest Path(最长异或路径)

这道题和上一道题思路差不多,可以转化成上一道题

设根结点为t,两个非跟节点为a,b,两节点之间的异或和为f
有f(a,b)=f(a,t)^f(b,t)

因此只需要写一个dfs函数,求出每个点到跟节点的异或和存下来,就可以把这个题转化成上一个题进行求解了

#include <bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int n;
int h[N],e[2*N],ne[2*N],w[2*N],idx;
int a[N];
int son[30*N][2],num=1;
void add(int a,int b,int c)
{
    e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
void dfs(int u,int p,int xs)
{
    a[u]=xs;
    for(int i=h[u];i!=-1;i=ne[i])
    {
        int j=e[i];
        if(j!=p) dfs(j,u,xs^w[i]);
    }
}
void insert(int x)
{
    int p=0;
    for(int i=30;i>=0;i--)
    {
        int u=x>>i&1;
        if(!son[p][u]) son[p][u]=num++;
        p=son[p][u];
    }
}
int query(int x)
{
    int p=0,res=0;
    for(int i=30;i>=0;i--)
    {
        int u=x>>i&1;
        if(son[p][!u])
        {
            p=son[p][!u];
            res=res*2+!u;
        }
        else
        {
            p=son[p][u];
            res=res*2+u;
        }
    }
    return res;
}
int main()
{
    memset(h,-1,sizeof(h));
    cin>>n;
    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);
    int res=0;
    for(int i=0;i<n;i++)
    {
        if(!i) insert(a[i]);
        else
        {
            int t=query(a[i]);
            res=max(res,a[i]^t);
            insert(a[i]);
        }
    }
    cout<<res<<endl;
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值