hash,kmp题组所有题解

目录

【模板】字符串哈希

【模板】KMP字符串匹配

于是他错误的点名开始了

A-B 数对

Barn Echoes G

Compress Words


【模板】字符串哈希

- [P3370 【模板】字符串哈希](【模板】字符串哈希 - 洛谷)

题解:对每个字符串进制哈希,并将哈希值存到ans数组里,统计里面不一样的值的个数,输出即可。

代码实现如下:

#include<bits/stdc++.h>
using namespace std;
char s[1501];
long long ans[10001],hashe[10001][1501];
int main()
{
    int n,base=131,number=1,len=0;
    long long mod=1e9+7;
    scanf("%d",&n);
    for(int i=1; i<=n; i++)
    {
        scanf("%s",s);
        len=strlen(s);
        //进制哈希
        for(int j=0; j<len; j++)
        {
            if(j==0)hashe[i][j]=s[j];
            else hashe[i][j]=(hashe[i][j-1]*base+s[j])%mod;
        }
        ans[i]=hashe[i][len-1];
    }
    sort(ans+1,ans+n+1);
    //统计数组ans中不相同的数字个数
    for(int i=1;i<n;i++)
    {
        if(ans[i]!=ans[i+1])
            number++;
    }
    printf("%d",number);
    return 0;
}

【模板】KMP字符串匹配

- [P3375 【模板】KMP字符串匹配](【模板】KMP字符串匹配 - 洛谷)

题解:这题就是一道KMP的模板题,输出匹配到的出现的所有位置,最后一行直接输出next数组即可

#include<stdio.h>
#include<string.h>
char s1[1000100],s2[1000100];
int next[1000100];
void Next()
{
    int n=strlen(s2+1);
    int j=0;
    for(int i=2;i<=n;i++)
    {
        while(j&&s2[j+1]!=s2[i])
            j=next[j];
        if(s2[j+1]==s2[i])j++;
        next[i]=j;
    }
}
void KMP()
{
    Next();//根据模式串T,初始化next数组
    int m=strlen(s1+1),n=strlen(s2+1);
    int j=0;
    for(int i=1;i<=m;i++)
    {
        while(j&&s2[j+1]!=s1[i])
            j=next[j];
        if(s2[j+1]==s1[i])j++;
        if(j==n)
        {
            printf("%d\n",i-n+1);
            j=next[j];
        }
    }
}
int main()
{
    scanf("%s",s1+1);
    scanf("%s",s2+1);
    KMP();
    for(int i=1;i<=strlen(s2+1);i++)
    {
        printf("%d ",next[i]);
    }
    return 0;
}

于是他错误的点名开始了

- [P2580 于是他错误的点名开始了](于是他错误的点名开始了 - 洛谷)

在思考了很久后,我也没有想出啥好的方法,于是看了下题解中说用map容器非常简单,后来了解到这是STL库里的内容,随后我又花时间去学了一下STL库里的string,set,map。vector暂时还没学,但解决这题也足够了。

题解:利用STL中的map容器将班上的人的名字作为键值,标记为1,然后扫一遍,如果为1,输出OK,同时将1更新为2,如果遇到大于1的,输出REPEAT,如果为0,说明没有,输出WRONG。

#include<bits/stdc++.h>
using namespace std;
string s;
map<string,int>mp;
int main()
{
    int n=0;
    scanf("%d",&n);
    for(int i=1; i<=n; i++)
    {
        cin>>s;//不能用scanf("%s")
        mp[s]++;
        s.clear();
    }
    int m=0;
    scanf("%d",&m);
    for(int i=1; i<=m; i++)
    {
        cin>>s;
        if(mp[s]==0)printf("WRONG\n");
        if(mp[s]==1)
        {
            printf("OK\n");
            mp[s]++;
        }
        if(mp[s]>1)printf("REPEAT\n");
        s.clear();
    }
    return 0;
}

A-B 数对

- [P1102 A-B 数对](A-B 数对 - 洛谷)

题解:假设他们可能有A,B双重身份。再把每个数(相当于是B)都加上C,扫一遍,看看有没有相等的A与之对应。

#include<bits/stdc++.h>
using namespace std;
int a[200001];
map<int,int>mp;
int main()
{
    int n=0,c=0;
    long long ans=0;
    scanf("%d%d",&n,&c);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        mp[a[i]]++;
    }
    for(int i=1;i<=n;i++)
    {
        ans+=mp[a[i]+c];//反向思维,枚举 B+C
    }
    printf("%lld",ans);
    return 0;
}

事实上在这个过程中我并没有那么顺利,因为刚开始我做的时候没有考虑到“不同位置的数字一样的数对算不同的数对”,所以我在映射时使mp[a[i]]=1,而不是mp[a[i]]++。

Barn Echoes G

- [P2957 [USACO09OCT]Barn Echoes G]([USACO09OCT]Barn Echoes G - 洛谷)

题解:利用string的内置函数substr,截取第一个字符串的前缀和第二个字符串的后缀,取最大相等长度i,截取第二个字符串的前缀和第一个字符串的后缀,取最大相等长度j。在i、j中取最大值即可。

#include<bits/stdc++.h>
using namespace std;
string s1,s2;
int main()
{
    cin>>s1;
    cin>>s2;
    int i,j;
    int l1=s1.size();
    int l2=s2.size();
    int minlen=min(l1,l2);
    for(i=minlen;i>0;i--)
    {
        if(s1.substr(0,i)==s2.substr(l2-i,i))//截取的也是string,可直接用==判定,string从0开始存,所以是l2-i
            break;
    }
    for(j=minlen;j>0;j--)
    {
        if(s2.substr(0,j)==s1.substr(l1-j,j))
            break;
    }
    int ans=max(i,j);
    printf("%d",ans);
    return 0;
}

Compress Words

- [CF1200E Compress Words](Compress Words - 洛谷)

题解:这个题目就是找第一个串的后缀和第二个串的前缀拼在一起,找到他们最长的公共部分拼在一起。这很容易让人联想到KMP中的next数组。那么让谁来作为模式串呢?我们可以把新加入的串放在前面,现有的串放在后面。特别注意的是最长公共前后缀的长度实际上只要到min(字符串1的长度,字符串2的长度)就行了,所以当现有长度大于新加入字符串长度(若新加入字符串长度为L)时,只要在现有字符串后面截取L长度接在新加入字符串后面就构成了模式串,还需要注意的是两个字符串中间需要加入一些特别的字符,以防最长公共前后缀超过待拼接串的长度。例如:

1001101       010

最后将101和010合并后KMP时出现了问题,合并后的字符串:010101

最长公共前后缀长度:4>待拼接串的长度3

最终代码实现如下:

#include<bits/stdc++.h>
using namespace std;
int nxt[1000001];//不能写next,next是关键字,会被认为有歧义
void Next(string s)//最长公共前后缀中前缀的最后一位位置
{
    int l=s.size();
    s=' '+s;//前面加个空格,让s下标从1开始
    int j=0;
    for(int i=2; i<=l; i++)
    {
        while(s[j+1]!=s[i]&&j)
            j=nxt[j];
        if(s[j+1]==s[i])j++;
        nxt[i]=j;
    }

}
int main()
{

    ios::sync_with_stdio(false);//让cin变得快一点
    int n;
    cin>>n;
    string s,ans;//从0开始存
    cin>>ans;
    for(int i=2; i<=n; i++)
    {
        cin>>s;
        int l=min(s.size(),ans.size());
        string ss=s+'#'+ans.substr(ans.size()-l,l);//把两个字符串隔开
        Next(ss);
        for(int j=nxt[ss.size()]; j<s.size(); j++) //实际上j=nxt[ss.size()-1+1],因为s从0开始存
            ans+=s[j];
    }
    cout<<ans;
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值