KMP、HASH题组题解

目录

P3375 【模板】KMP字符串匹配

 P2957 [USACO09OCT]Barn Echoes G

 P3370 【模板】字符串哈希

 P1102 A-B 数对

 P2580 于是他错误的点名开始了

 CF1200E Compress Words


P3375 【模板】KMP字符串匹配

思路分析:

  为了方便,字符串从1开始。首先我们需要对记录每个前缀长的数组进行赋值。由于是真前缀,因此第一个字符的border长度为0,从第二个字符开始,首先判断如果j大于0,由于后面判断的是j的下一个字符,也就是大于1个字符的时候,也就是说如果只有两个字符,第二个字符和第一个字符都不匹配的话,此时j就没有必要移动的,因为本身就是第一位了。如果现在已经匹配了大于等于1个字符,但是下一个字符不匹配的话,就移动j到它前缀的位置,直到恰好与当前位置所匹配为止,最多到第一位。如果匹配的话,j就加1,代表前缀的长度。

  第二步是求s2在s1中的位置,和初始化prefix数组一样,只不过一个是自己匹配自己,一个是二者匹配,只需要加一个判断当前j如果等于s2长度的话,就代表s1中存在s2,此时输出s2在s1中的位置即可,此时还需要对j进行移位,因为s1中可能还会有第二个s2并且与当前s2有重合。最后输出前缀数组即可。

代码实现: 

#include<iostream>
#include<algorithm>
#include<string.h>
using namespace std;
char s1[2000000];
char s2[2000000];
int prefix[2000000];//前缀数组
int length1,length2;
int i,j;
int main()
{
     cin>>s1+1>>s2+1;
     length1=strlen(s1+1);//因为是从1开始存储,所以也是从1开始计算长度
     length2=strlen(s2+1);
     for(i=2;i<=length2;i++)//初始化prefix数组
     {
         while(j&&s2[i]!=s2[j+1])
         j=prefix[j];
         if(s2[i]==s2[j+1])
         j++;
         prefix[i]=j;
     }
     for(int i=1,j=0;i<=length1;i++)
     {
         while(j&&s1[i]!=s2[j+1])//在已经匹配的情况,下一个字符不匹配,就要移位。如果s2第一个字符就不匹配,那么s2整体右移,也就是s1的比较位置i加1
         j=prefix[j];
         if(s1[i]==s2[j+1])//如果相等,匹配的长度就加1
         j++;
         if(j==length2)//如果相等,代表匹配成功
         {cout<<i-length2+1<<endl;
           j=prefix[j];
         }
     }
     for(i=1;i<=length2;i++)
        cout<<prefix[i]<<" ";

     return 0;
}

 P2957 [USACO09OCT]Barn Echoes G

 思路分析:

  这道题很简单,就是求出两个字符串的重复部分同时是一个字符串的前缀和另一个字符串的后缀,求最长的重复部分。因此我们只需要考虑两种情况。第一种情况是第一个字符串的前缀和第二个字符串的后缀的重合部分,第二种情况是第一个字符串的后缀和第二个字符串的前缀的重合部分。输出二者较大者即可。

  注意这里存在包含的情况,即较长字符串中存在较短字符串。所以我们直接从较短字符串的长度开始,因为较短字符串的长度就是最大的重复长度,如果能够匹配,就直接跳出,并存储长度。

代码实现: 

#include<iostream>
#include<algorithm>
#include<string.h>
using namespace std;
char s1[10010];
char s2[10010];
int length1,length2,length;
int max1,max2;
int i,j;
int main()
{
    cin>>s1>>s2;
    length1=strlen(s1);
    length2=strlen(s2);
    length=length1<length2?length1:length2;//存储两个字符串中的较短长度
    for(i=length;i>=1;i--)//求第一个字符串前缀和第二个字符串后缀的重复部分
     {
         for(j=0;j<i;j++)
         {
             if(s1[j]!=s2[length2-i+j])
             break;
         }
         if(j==i)break;//如果相等,则证明匹配成功,直接跳出
    }
     max1=i;
     for(i=length;i>=1;i--)//求第一个字符串后缀和第二个字符串前缀的重复部分
     {
         for(j=0;j<i;j++)
         {
             if(s2[j]!=s1[length1-i+j])
             break;
         }
         if(i==j)break;
     }
     max2=i;
     int ans;
     ans=max1>max2?max1:max2;
     cout<<ans;
     return 0;
}

 P3370 【模板】字符串哈希

思路分析: 

  这里就是引入一个进制,把字符串的每一个元素看成进制位,也就是通过运算每个字符串都能变成一串数字,为了不重复,进制base一般取131或者133331。转换完之后从小到大排序,如果前一个与后一个不相等,不同字符串的个数就+1。需要注意的是,这里要循环到最后一位,因为如果所有字符串都是一样的话并且计数器是从0开始的,此时最后一个字符串就会和越界的元素比较,此时计数器会+1。

代码实现:

#include<bits/stdc++.h>
using namespace std;
char s[50000];
int base=131;//进制位
int a[50000];
int sum;
void swap(int *a,int *b)
{
    int k;
    k=*a;
    *a=*b;
    *b=k;
}
void quick(int begin,int end)//快速排序
{
    int mid=a[(begin+end)/2];
    int left=begin,right=end;
    do{
        while(a[left]<mid) left++;
        while(a[right]>mid) right--;
        if(left<=right)
        {
            swap(&a[left],&a[right]);
            left++;
            right--;
        }
    }while(left<=right);
    if(begin<right) quick(begin,right);
    if(left<end) quick(left,end);
}
int ha(char s[])
{
    int k=0;
    for(int i=0;i<strlen(s);i++)
    {
        k=k*base+(s[i]-'0');//按照进制的方法转换成整型的数字
    }
    return k;
}
int main()
{
    int n;
    cin>>n;
    for(int i=0;i<n;i++)
     {
         cin>>s;
         a[i]=ha(s);//将输入的字符串转换成哈希值存放数组当中
     }
  quick(0,n-1);//利用快速排序对元素进行从小到大的排序
  for(int i=0;i<n;i++)
  {
    if(a[i]!=a[i+1])sum++;
  }
   cout<<sum;
      return 0;
}

 P1102 A-B 数对

思路分析: 

   这里我们采用二分的方法。题目需要求出A-B=C的数对的个数,其实可以转换成A+C=B的数对的个数。首先把输出的数字从小到排序,从第一个开始,把其当作A,然后寻找从第二个到最后一个数字这个区间中A+C>B的个数,然后再找A+C>=B的个数,后者-前者就是第一个数作为A的时候,A+C=B的个数,也就是可以找到多少个B。定义一个累加器,把每一位的B的个数加起来就是需要求的数对的个数。

 代码实现:

#include<bits/stdc++.h>
using namespace std;
int a[1000010];
int n,c,l,r,mid;
long long sum1,sum2,sum;
int main()
{
    cin>>n>>c;
    for(int i=0;i<n;i++)
    {
        cin>>a[i];
    }
    sort(a,a+n);//利用sort函数从小到大进行排序
    for(int i=0;i<n;i++)
    {
       l=i+1;
       r=n-1;
       while(l<=r)
       {
           mid=(l+r)/2;
           if(a[i]+c<=a[mid])r=mid-1;
           else l=mid+1;
       }
       sum1=l;//l代表的就是当第i位作为A时,有多少个B满足A+C>B
       l=i+1;
       r=n-1;
       while(l<=r)
       {
           mid=(l+r)/2;
           if(a[i]+c<a[mid])r=mid-1;
           else l=mid+1;
       }
       sum2=l;//l代表的就是当第i位作为A时,有多少个B满足A+C>=B
       sum+=sum2-sum1;
    }
    cout<<sum;
    return 0;
}

 P2580 于是他错误的点名开始了

思路分析: 

   这里我们可以利用类似桶排的方法,将输入的字符串标记为1,其他没有被标记的默认为0.然后点名的时候,如果该字符串被标记为1,就输出OK,并且标记为2。如果该字符串被标记为2,就输出REPEAT。如果没有被标记,就输出WRONG。可是数组的下表是整型,字符串是字符型,那么可以用到STL库中的map函数,简单来说就是一个两种数据类型的映射,这里我们可以把输入的字符串映射成一个整型的数字,把其当作标记数组的下标。

 代码实现:

#include<bits/stdc++.h>
using namespace std;
char s[100010];
int n,m;
map<string,int>name;//定义map从string到int类型的映射
int main()
{
    cin>>n;
    for(int i=0;i<n;i++)
    {
        cin>>s;
        name[s]++;
    }
    cin>>m;
    for(int i=0;i<m;i++)
    {
        cin>>s;
        if(name[s]==1)
        {
            puts("OK");
            name[s]=2;
        }
        else if(name[s]==2)
        {
            puts("REPEAT");
        }
        else puts("WRONG");
    }
    return 0;
}

 CF1200E Compress Words

 

思路分析: 

   这道题可以采用kmp的方法来解决。首先可以先输入第一个的单词并记录下长度,然后输入第二个单词,求出其前缀数组。如果原长度比待插入的单词的长度要短,那么就从下标为1的位置开始匹配,反之从末尾预留待插入单词的长度进行匹配。然后每匹配成功一次,就代表原字符串中含有待插入单词的当前字母,那么待插入的单词就往后移一个字母,后面的插入操作时,前面的字母就不用重复插入了。

 代码实现:

#include<bits/stdc++.h>
using namespace std;
char s1[1000010];
char s2[1000010];
int n;
int nex[1000010];
int length1,length2;
int main()
{
    cin>>n;
    cin>>s1+1;
    length1=strlen(s1+1);
    for(int k=1;k<n;k++)
    {
        cin>>s2+1;
        length2=strlen(s2+1);
        nex[0]=0;
        nex[1]=0;
       int j=0;
        for(int i=2;i<=length2;i++)//初始化nex前缀数组
        {
            while(j&&s2[i]!=s2[j+1])j=nex[j];
             if(s2[i]==s2[j+1])j++;
             nex[i]=j;
        }
       j=0;
       int p;
       if(length1-length2<0)p=1;//判断原长度是否小于待插入单词的长度
       else p=length1-length2+1;
       for(int i=p;i<=length1;i++)
       {
           while(j&&s1[i]!=s2[j+1])j=nex[j];
           if(s1[i]==s2[j+1])j++;
       }
       for(;j<length2;j++)
       s1[++length1]=s2[j+1];
    }
    cout<<s1+1;
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值