题意:
查找一个字符串在另一个字符串中出现的次数.
Sample Input
3
BAPC
BAPC
AZA
AZAZAZA
VERDI
AVERDXIVYERDIAN
Sample Output
1
3
0
Sol:典型的字符hash.
Std1:取mod
#include<bits/stdc++.h> #define maxn 1e6+10 using namespace std; const int mod=19260817; long long p[1000001],sum[1000001],num; int n; int b=193; char c1[100001],c2[1000001]; int main() { p[0]=1; for(int i=1;i<=maxn;i++)// 求出b^i p[i]=p[i-1]*b%mod; cin>>n; while(n--) { int ans=0; num=0; sum[0]=0; scanf("%s %s",c1+1,c2+1); //查找c1串在c2中出现的次数 int len1=strlen(c1+1); int len2=strlen(c2+1); for(int i=1;i<=len1;i++)//计算c1串的hash值 num=((num*b%mod)+(c1[i]-'A'+1))%mod; for(int i=1;i<=len2;i++)//将c2串的前若干位的值计算出来 sum[i]=((sum[i-1]*b%mod)+(c2[i]-'A'+1))%mod; for(int i=0;i<=len2-len1;i++) if(num==((sum[i+len1]-sum[i]*p[len1]%mod)+mod)%mod) ans++; printf("%d ",ans); } return 0; }
Std2:用无符号整数计算hash值,用自然溢出求掉求模运算
#include<bits/stdc++.h> #define maxn 1e6+10 using namespace std; typedef unsigned long long ull; ull p[1000001],sum[1000001],Num; int n; int b=193; char c1[100001],c2[1000001]; void read(int &x) { char ch; bool ok; for(ok=0,ch=getchar(); !isdigit(ch); ch=getchar()) if(ch=='-') ok=1; for(x=0; isdigit(ch); x=x*10+ch-'0',ch=getchar()); if(ok) x=-x; } int main() { p[0]=1; for(int i=1;i<=maxn;i++) p[i]=p[i-1]*b; read(n); while(n--) { int ans=0; Num=0; sum[0]=0; cin>>c1+1>>c2+1; int len1=strlen(c1+1); int len2=strlen(c2+1); for(int i=1;i<=len1;i++) Num=Num*b+ull(c1[i]-'A'+1); for(int i=1;i<=len2;i++) sum[i]=sum[i-1]*b+ull(c2[i]-'A'+1); for(int i=0;i<=len2-len1;i++) if(Num==sum[i+len1]-sum[i]*p[len1]) ans++; printf("%d ",ans); } return 0; }
Sol3:Kmp
/*
本题匹配出来的子串是可以有交集的.
另一个变形题来自Hdu2087 剪花布条
其要求子串,不能有交集
*/
#include<cstdio>
#include<cstring>
using namespace std;
int pre[10010],t,n,m,ans;
char a[10010],b[1000010];
void get_next()
{
int j=0;
for (int i=1;i<n;i++)
{
while (j>0&&a[j+1]!=a[i+1]) j=pre[j];
if (a[j+1]==a[i+1]) j++;
pre[i+1]=j;
}
}
void get_ans()
{
ans=0;
int j=0;
//认为母串的第i位已与子串的第j位匹配成功
//接下来试图找子串的第j+1位与母串的第i+1位匹配
for (int i=0;i<m;i++)
{
while (j>0&&a[j+1]!=b[i+1])
j=pre[j];
if (a[j+1]==b[i+1])
//如果找成功则j++,注意此时i的值还没有变化
//等到下一轮时即i++后称之为i',i'与j是匹配成功(在当前这一轮保证的)
//则如果我们希望继续匹配下去,就有两种情况
//一种是找出来的子串有交集,则取j=pre[j]
//另一种是没有交集,则说明i'这个字符是不能被匹配到的
//即我们不能让子串中任一个字符与之匹配,于是j=0
j++;
if (j==n)
{
j=pre[j];
ans++;
}
}
printf("%d
",ans);
}
int main(){
scanf("%d",&t);
while (t--){
memset(pre,0,sizeof(pre));
scanf("%s",a+1);
n=strlen(a+1);
scanf("%s",b+1);
m=strlen(b+1);
get_next();
get_ans();
}
return 0;
}
Sol4:扩展Kmp
#include<cstdio> #include<cstdlib> #include<cstring> #include<cmath> #include<iostream> #include<algorithm> using namespace std; char S[11000005], T[11000005]; int Slen, Tlen, nxt[11000005], lcp[11000005]; int main() { int run1; cin>>run1; for (int rr=1;rr<=run1;rr++) { scanf("%s", T);//子串 scanf("%s", S);//母串 Slen = strlen(S), Tlen = strlen(T); nxt[0] = Tlen; memset(lcp,0,sizeof(lcp)); for(register int i = 1, a = 0, p = 0; i < Tlen; i++) { if(i + nxt[i - a] >= p) { if(i > p) //有可能输入的两个字符串完全不相等,那p的值就要等于i //也就是说始终间将第一个字符串的第i个字符与第二个字符串的第一个字符开始比 //p代表从前比较时,最靠左的失效位置, 在我们的讨论中一直认为i<p,所以当i>p时,p=i //于是从从前的失效位置开始比较,即从t[p]开始与t[p-i]开始比较, p = i; while(p < Tlen && T[p] == T[p - i]) p++; nxt[i] = p - i, a = i; } else nxt[i] = nxt[i - a]; } for(register int i = 0, a = 0, p = 0; i < Slen; i++) { if(i + nxt[i - a] >= p) { if(i > p) p = i; while(p < Slen && S[p] == T[p - i]) p++; lcp[i] = p - i, a = i; } else lcp[i] = nxt[i - a]; } int ans=0; for(register int i = 0; i <= Slen - 1; i++) if (lcp[i]==Tlen) ans++; printf("%d ", ans); } return 0; }
类似的习题有Poj2406 Power String,此题还可以用Kmp算法来做
#include<iostream> #include<cstdio> #include<string> #include<cstring> using namespace std; typedef unsigned long long LL; const LL base=131; const int N=1000010; int n; LL power[N],sum[N]; bool check(LL v,int k) //判断s[1]~s[k]是否是循环节 { for(register int i=1;i<=n;i+=k) //一路推过去每k位都应该值等于v { if(v!=sum[i+k-1]-sum[i-1]*power[k]) return 0; } return 1; } int main() { power[0]=1; for(register int i=1;i<=N-10;++i) //hash准备工作 power[i]=power[i-1]*base; char s[N]; while(scanf("%s",s+1)) { if(s[1]=='.')break; n=strlen(s+1); sum[0]=0; for(register int i=1;i<=n;++i) sum[i]=sum[i-1]*base+LL(s[i]); for(register int i=1;i<=n;++i) { if(n%i) //循环节长度i,应为n的约数 continue; LL expect=sum[i]; if(check(expect,i)) { printf("%d ",n/i); break; } } } return 0; }
HDU 2087 剪花布条
一块花布条,里面有些图案,另有一块直接可用的小饰条,里面也有一些图案。对于给定的花布条和小饰条,计算一下能从花布条中尽可能剪出几块小饰条来呢?
输入格式
输入数据为多组数据,读取到 # 字符时结束。每组数据仅有一行,为由空格分开的花布条和小饰条。花布条和小饰条都是用可见 ASCII 字符表示的,不会超过1000 个字符。
注意:这个 # 应为单个字符。若某字符串开头有 #,不意味着读入结束!
输出格式
对于每组数据,输出一行一个整数,表示能从花纹布中剪出的最多小饰条个数。
样例
样例输入
abcde a3
aaaaaa aa
#
样例输出
0
3
#include <bits/stdc++.h> using namespace std; int ans, len1, len2; int next[1000001]; char s1[1000001]; char s2[1000001]; void gnext() { next[1] = 0; for (int i = 1, j = 0; i < len2; i++) { while (j && s2[i + 1] != s2[j + 1]) j = next[j]; if (s2[i + 1] == s2[j + 1]) j++; next[i + 1] = j; } } void KMP() { for (int i = 0, j = 0; i < len1; i++) { while (0 < j && s1[i + 1] != s2[j + 1]) j = next[j]; if (s1[i + 1] == s2[j + 1]) j++; if (j == len2) { ans++; j = 0; } } } int main() { while (cin >> s1 + 1) { ans = 0; if (s1[1] == '#' && strlen(s1 + 1) == 1) return 0; cin >> s2 + 1; len1 = strlen(s1 + 1); len2 = strlen(s2 + 1); gnext(); KMP(); cout << ans << endl; } }