KMP(字符串匹配)+字符串哈希

KMP非常不好理解,建议在网上搜KMP的视频看看,反正非常难理解,我想了好久好久(KMP的关键就在于求Next数组D,求前缀后缀)
下面的D题,B题稍稍有变化 ,C题只用求前缀后缀就只用求next数组

标准求next数组做法,
  int len=moshi.length();
	  n[0]=-1;
	  int k=-1;
	  int j=0;
	  while(j<len-1){
	      if(k==-1||moshi[j]==moshi[k]){
	      ++k;
	      ++j;
	      n[j]=k;
	    }else{
	      k=n[k];
	    }
	}

问题 A: 统计不同字符串的个数

题目描述
如题,给出n个字符串,让你输出其中共有多少个不同的字符串。请使用字符串哈希解决本题。
输入
第一行一个正整数n表示有n个字符串。
接下来n行每行一个字符串,每个字符串可以由数字和大小写字母组成,大小写敏感。
1 ≤ n ≤ 1000,每个字符串最大长度为1500
输出
输出一行包含一个整数,表示其中不同字符串的个数。
样例输入
5
abc
aaaa
abc
abcc
12345
样例输出
4

本题用哈希的方法做:一个字符串对应一个哈希值,然后把这个哈希值对应到一个数组的坐标上。
#include<bits/stdc++.h>
using namespace std;
const int maxn=10000+5;
#define p 13
#define mod 101
set<string> ss;
int a[maxn];

int hashh(string s){
    int len=s.length();
    int sum=0;  //哈希值
    for(int i=0;i<len;i++){
      sum+=(s[i]*p)%mod;
    }
    if(a[sum]==0){   //对应到数组左标上  为0说明以前没加过,然后赋值为一,返回值为1
       a[sum]=1;
       return 1;
     }
    else
    return 0     //否则说明这个字符串以前加过;返回零
}
int main(){
    string s;
    int n;
    while(cin>>n){
      memset(a,0,sizeof(a));
      for(int i=0;i<n;i++){
        if(hashh(s))   //为1时说明以前没加过,然后加到set里(用vector也行)
            ss.insert(s);
      }
      cout<<ss.size()<<endl;  //然后直接用set的大小值输出
    }
}

//下面这个为标准做法
#include <cstdio>
#include <iostream>
#include <map>
#include <set>
#include <cstring>
using namespace std;
const int maxn = 1507;
const int p = 13;
const int mod = 101;
int Hash[maxn];
int main()
{
    int n;
    cin >> n;
    set<int> ss;
    while(n--){
        string tmp;
        cin >> tmp;
        int i;
        for(i = 0; i < tmp.size(); i++){
            if(i == 0) Hash[i] = int(tmp[i] - 'a');
            else Hash[i] = (Hash[i-1] * p + int(tmp[i] - 'a' + 1)) % mod;
        }
        ss.insert(Hash[i-1]);
        memset(Hash, 0, sizeof Hash);
    }
    cout << ss.size() << endl;
    return 0;
}

问题 B: 赵神的兼职

题目描述
赵神最近在做一份兼职——帮人查一串字符串中某个单词的出现次数。
当工作太多时,赵神就忙不过来啦,所以他想找一名助手帮他。
输入
第一行输入一个 T ,代表数据数目。
每组数据第一行输入需要查找的单词。
第二行输入这个较长的字符串(长度小于1000010)。
输出
每组数据输出一个 n ,代表要这个单词出现的次数。
样例输入
3
ABCD
ABCD
AZA
AZAZAZA
HIDSJ
FJOSJWHABNMDS
样例输出
1
3
0

kmp算法做,但是也不完全和kmp一样,因为第二种情况按kmp是应该输出2的,但是他是3,是因为他是统计这个出现次数
相当于j走完了i并没有j加一位 ,而是直接在这一位上继续比较,
                              那这个情况就变成next数组多求一位
AZA                           AZAx                x是空格,他肯定是不匹配的,他会移动到第二个位置
  j                              j                    AZAX
AZAZAZA                       AZAZAZA               AZAZAZA   
  i                             i 
#include<bits/stdc++.h>
using namespace std;
string moshi,mubiao;
const int maxn=1000010;
int n[maxn];

void init(){     //求next数组   根据next[j]=k(j位置发生失配时,回到位置k)求next[j+1]等于多少
     memset(n,0,sizeof(n));
  int len=moshi.length();
  n[0]=-1;     //next数组的第一个赋值为-1
  int k=-1;   //第一次k赋值为1
  int j=0;  //下标从0开始
  while(j<len){   //kmp本来是小于len-1,多求一位就变成小于len
    if(k==-1||moshi[j]==moshi[k]){   //k=-1说明此时回到的是头部 moshi[j]=moshi[k]说明有后缀和前缀
      ++k;                           //此时当前k+1;
      ++j;                            //j的位置走加1
      n[j]=k;                          //将K的值赋值给n[j];
    }else{  //否则k的值变为回到位置上的值
      k=n[k];
    }
  }
}

int kmp(){
   int lenmoshi=moshi.length(),lenmubiao=mubiao.length();
   int i=0,j=0,sum=0;
   while(i<lenmubiao){    //走目标串
          if(mubiao[i]==moshi[j]){     //如果目标串的对应元素和模式串的对应元素相等    
                i++;
                j++;
          }else if(j>=0){//回到位置是值是大于等于0的
                j=n[j];  //失配时,模式串的位置变到n数组位置上的值     失配时,模式串移到哪   失配时,先走模式串先走回到哪,在进行匹配
          }else{  //代表j=-1了说明第一个就发生了失配  目标串的位置+1 
             ++i;
             ++j;//j+1变为0  下一次进入循环就变为mubiao[i]是否等于moshi[0]
             }
          if(j==lenmoshi){   //走完了模式串
                sum++;
                }
   }
   return sum;
}
int main(){
        int n;
        cin>>n;
        while(n--){
          cin>>moshi>>mubiao;
           init();
           cout<<kmp()<<endl;
        }
}

问题 C: 测姻缘

题目描述
小林最近开始研究算命。
虽然他不会算命,但是他可以自己创造一个算命的算法
当一个人的名字的前缀,与另一个人的名字后缀,相同的字母越多,这两个人就越有可能成为情侣。(此处前后缀可以包括名字本身)。
这个算姻缘的方法使得许多妹子前来测试。然而,由于他最近忙着写线段树,没空帮人算姻缘啦。
这里还有许多没测完呢。你能帮帮美丽的妹子们算算吗?
输入
输入有多组数据。
每组数据有两行,分别为两个人的名字(名字全由英文组成,名字长度不超过50000)。
输出
求出前一个人的名字的前缀,与后一个人的名字的后缀,最大的相同数目。
若不为0,还需输出其相同的几位字母,并且字母在数字前面,中间由空格隔开。
样例输入
mike
aniom
kiava
dvakia
dasds
fdsgh
样例输出
m 1
kia 3
0

#include<bits/stdc++.h>
using namespace std;
const int maxn=50000+5;
int Next[maxn];
string s1,s2;

void inint_next(string s){
  memset(Next,0,sizeof(Next));

}

int main(){
    string s1,s2;
    while(cin>>s1>>s2){
       string s=s1+s2;
      // cout<
      int len=s.length();
      int len1=s1.length();
      int j=0,k=-1;
      Next[0]=-1;
      while(j<len){
        if((k==-1||s[k]==s[j])&&k<len1){
          ++k;
          ++j;
          Next[j]=k;
        }else{
          k=Next[k];
        }
      }
       //for(int i=0;i
         //cout<
    //   }
    //   cout<
       int cnt=Next[len];
       for(int i=0;i<cnt;i++){
         cout<<s[i];
       }
       if(cnt!=0) cout<<" ";
       cout<<cnt<<endl;
    }
}

问题 D: 左左的牛肉串

题目描述
左左有N串长度比较短的牛肉串,俊俊有一串长度比较长的牛肉串。牛肉串都是由字符组成,俊俊想知道左左的每一串牛肉串在自己的牛肉串中出现的次数。
输入
输入多组数据。
输入一个数字N代表左左的N串牛肉串。1 <= N <= 10。
接下来N行为左左的牛肉串中的字符。字符串长度不超过10。
第N+1行,俊俊的牛肉串中的字符。字符串长度不超过100。
输出
输出一个数字代表左左的N串牛肉串在俊俊的牛肉串中出现的次数。
样例输入
5
she
he
say
shr
her
yasherhs
样例输出
3


kmp算法
#include<bits/stdc++.h>
using namespace std;
string mubiao;
const int maxn=1000010;
int n[maxn];
vector<string> v;
void initnext(string moshi){
     memset(n,0,sizeof(n));
  int len=moshi.length();
  n[0]=-1;
  int k=-1;
  int j=0;
  while(j<len-1){
    if(k==-1||moshi[j]==moshi[k]){
      ++k;
      ++j;
      n[j]=k;
    }else{
      k=n[k];
    }
  }
}

int kmp(string moshi){
   int lenmoshi=moshi.length(),lenmubiao=mubiao.length();
   int i=0,j=0,sum=0;
   while(i<lenmubiao-1){   
          if(j==-1||mubiao[i]==moshi[j]){     
                i++;
                j++;
          }else{
                j=n[j];  
          }
          if(j==lenmoshi)
                sum++;
   }
   return sum;
}
int main(){
        int n;
        cin>>n;
        int ans=0;
        string moshi;
        while(n--){
          cin>>moshi;
           v.push_back(moshi);
        }
        cin>>mubiao;
        vector<string> ::iterator it;
        for(it=v.begin();it!=v.end();it++){
            initnext(*it);
            ans+=kmp(*it);
        }
        cout<<ans<<endl;
}

问题 E: 红红的小兔子

题目描述
红红的棚子里养着一群兔子。
有一天,红红想要研究兔子的 DNA 序列和兔子的长相。
首先选取一个好长好长的 DNA 序列(小兔子是外星生物,DNA 序列可能包含 26 个小写英文字母)。
然后每次选择两个区间,询问如果用两个区间里的 DNA 序列分别生产出来两只兔子,这两个兔子是否一模一样。
注意两个兔子一模一样只可能是他们的 DNA 序列一模一样。
请使用字符串hash

输入
第一行输入一个 DNA 字符串 S。
第二行一个数字 m,表示 m 次询问。
1 ≤length(S)m≤1000000
接下来 m 行,每行四个数字 L1 R1 L2 R2,分别表示此次询问的两个区间,注意字符串的位置从1开始编号。
输出
对于每次询问,输出一行表示结果。
如果两只兔子完全相同输出 Yes,否则输出 No(注意大小写)。
样例输入
aabbaabb
3
1 3 5 7
1 3 6 8
1 2 1 2
样例输出
Yes
No
Yes

//哈希做法
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int maxn = 1000015;
const int p = 1e5+3;       
//一般如果数据比较多比较大的话,p可以取一个六位数的素数,mod取1e9+7这样子
const int mod = 1e9+7;
char ch[maxn];
LL h[maxn], pi[maxn];

LL get(int l, int r)   //求一段区间的哈希值
{
    return ((h[r] - h[l - 1] * pi[r - l + 1]) % mod + mod) % mod;
}

int main()
{
    scanf(" %s", ch + 1);
    memset(h, 0, sizeof h);
    int n = strlen(ch + 1); //从ch[1]开始存
    pi[0] = 1;
    for (int i = 1; i <= n; i++) {
        h[i] = (h[i - 1] * p + ch[i] - 'a' + 1) % mod; //利用前缀和的思想求哈希值
        pi[i] = (pi[i - 1] * p) % mod;
    }
    int m;
    cin >> m;
    while (m--) {
        int l, r, x, y;
        cin >> l >> r >> x >> y;
        if (get(l, r) == get(x, y))
            cout << "Yes" << endl;
        else
            cout << "No" << endl;
    }
    return 0;
}

//利用截取字符串方法做
#include<bits/stdc++.h>
using namespace std;

int main(){
  string s;
  cin>>s;
  int n;
  cin>>n;
  while(n--){
    int abegin,aend,bbegin,bend;
    cin>>abegin>>aend>>bbegin>>bend;
    string s1,s2;
    s1=s.substr(abegin-1,aend-abegin+1);
    s2=s.substr(bbegin-1,bend-bbegin+1);
  //  for(int i=abegin-1;i
  //    s1+=s[i];
  //  for(int j=bbegin-1;j
  //      s2+=s[j];
  if(s1==s2)
           cout<<"Yes"<<endl;
         else
           cout<<"No"<<endl;
  }
}

你可能感兴趣的:(ACM,acm,kmp)