题意
给你三个正整数你n,m,q,给你一个长度为n的字符串和长度为m的字符串,还有q次询问。
问在第一个字符串的l,r的区间内,可以找到几个第二个字符串。
思路
优化substr
我们开始尝试暴力来写(万一数据水呢)
时间复杂度o(q*(r-l)*len2)
暴力代码
#include <bits/stdc++.h>
#define ll long long
#define fi first
#define se second
#define pb push_back
#define me memset
const int N = 1e6 + 10;
const int mod = 1e9 + 7;
const int INF = 0x3f3f3f3f;
using namespace std;
typedef pair<int,int> PII;
typedef pair<ll,ll> PLL;
int main()
{
int n,m,q;
cin>>n>>m>>q;
string s1,s2;
cin>>s1>>s2;
int len1=s1.length();
int len2=s2.length();
while(q--)
{
int l,r;
cin>>l>>r;
int cnt=0;
for(int i=l-1 ; i<=r-len2 ; i++)
{
// cout<<i<<endl;
string s=s1.substr(i,len2);
// cout<<s<<endl;
if(s==s2) cnt++;
}
cout<<cnt<<endl;
}
return 0;
}
最后的极端情况下时间复杂度应该是1e8*len2,这样肯定是不行的。
我当时一直在想有什么办法可以优化没有,后来我想到substr被重复的算了好多次,我就把所有的区间都预处理出来,这样每次判断两个区间是否相等的时间就是o(1)。
时间复杂度o((r-l)*q)=1e8,给两秒的时间是可以过的。
代码
#include <bits/stdc++.h>
#define ll long long
#define fi first
#define se second
#define pb push_back
#define me memset
const int N = 1e6 + 10;
const int mod = 1e9 + 7;
const int INF = 0x3f3f3f3f;
using namespace std;
typedef pair<int,int> PII;
typedef pair<ll,ll> PLL;
bool vis[1010][1010];
int main()
{
int n,m,q;
cin>>n>>m>>q;
string s1,s2;
cin>>s1>>s2;
int len1=s1.length();
int len2=s2.length();
for(int i=0 ; i<=len1-len2 ; i++)
{
string s=s1.substr(i,len2);
if(s==s2) vis[i][i+len2-1]=true;
}
while(q--)
{
int l,r;
cin>>l>>r;
int cnt=0;
for(int i=l-1 ; i<=r-len2 ; i++)
{
if(vis[i][i+len2-1]) cnt++;
}
cout<<cnt<<endl;
}
return 0;
}
维护前缀数组
我们可以维护一个前缀数组,这样每次给出区间l,r的时候我们就可以用维护出来的值来直接判断。
ans=pre[r-m+1]-pre[l-1].
昨天晚上一直在想这个前缀数组,但是一直没有找到。
pre[i]表示0~i+m-1的范围内有多少个子串。
代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <vector>
#include <map>
#include <queue>
#define maxn 1005
#define INF 0x3f3f3f3f
#define LL long long
using namespace std;
int n,m,k;
string s,t;
int pre[maxn];
int main()
{
cin>>n>>m>>k;
cin>>s>>t;
s=" "+s;
t=" "+t;
for(int i=1;i<=n;i++)//计算前缀
{
int flag=1;
for(int j=1;j<=m;j++)//匹配子串来确定前缀值
{
if(s[i+j-1]!=t[j])
{
flag=0;
break;
}
}
if(flag==1)
{
pre[i]=pre[i-1]+1;
}
else
pre[i]=pre[i-1];
}
for(int i=0;i<k;i++)
{
int l,r;
cin>>l>>r;
if(r-l+1<m)
cout << 0 << endl;
else
cout << pre[r-m+1]-pre[l-1] << endl;
}
return 0;
}
二分区间
我们可以用两个数组L,R来分别存放每个找到子串的时候的第一个字母的下标和最后一个字母的下标。
然后我们每次读入一个l,r的时候我们就判断l在L数组中的位置,用二分找到第一个大于等于l的位置,r在R数组中的位置,用二分找到第一个大于r的位置。
然后相减就是答案了。
#include<algorithm>
#include<set>
#include<vector>
#include<queue>
#include<cmath>
#include<cstring>
#include<iostream>
#include<set>
#include<vector>
#include<queue>
#include<cmath>
#include<cstdio>
#include<map>
#include<stack>
#include<string>
#include<bits/stdc++.h>
using namespace std;
#define sfi(i) scanf("%d",&i)
#define pri(i) printf("%d\n",i)
#define sff(i) scanf("%lf",&i)
#define ll long long
#define ull unsigned long long
#define mem(x,y) memset(x,y,sizeof(x))
#define INF 0x3f3f3f3f
#define eps 1e-16
#define PI acos(-1)
#define lowbit(x) ((x)&(-x))
#define zero(x) (((x)>0?(x):-(x))<eps)
#define fl() printf("flag\n")
#define MOD(x) ((x%mod)+mod)%mod
#define endl '\n'
#define pb push_back
#define lson rt<<1,l,mid
#define rson rt<<1|1,mid+1,r
#define FAST_IO ios::sync_with_stdio(false);cin.tie(0);cout.tie(0)
const int maxn=1e6+9;
const int mod=1e9+7;
inline ll read()
{
ll f=1,x=0;
char ss=getchar();
while(ss<'0'||ss>'9')
{
if(ss=='-')f=-1;ss=getchar();
}
while(ss>='0'&&ss<='9')
{
x=x*10+ss-'0';ss=getchar();
} return f*x;
}
ll power(ll x,ll n)
{
ll ans=1;
while(n)
{
if(n&1) ans=ans*x;
x=x*x;
n>>=1;
}
return ans;
}
vector<int >L,R;
int main()
{
//FAST_IO;
//freopen("input.txt","r",stdin);
int n,m,q;
cin>>n>>m>>q;
string s,t;
cin>>s>>t;
for(int i=0;i<n;i++)
{
bool f=1;
for(int j=0;j<m;j++)
{
if(s[i+j]!=t[j])
{
f=0;
break;
}
}
if(f)
{
L.push_back(i);
R.push_back(i+m-1);
//cout<<i<<" "<<i+m-1<<endl;
}
}
L.push_back(INF);
R.push_back(INF);
while(q--)
{
int l,r;
cin>>l>>r;
l--;
r--;
int k=lower_bound(L.begin(),L.end(),l)-L.begin();
int kk=upper_bound(R.begin(),R.end(),r)-R.begin();
cout<<k<<" "<<kk<<endl;;
cout<<max(0,kk-k)<<endl;
}
return 0;
}
KMP
我们知道KMP的作用是求一个子串,在主串出现的下标,
我们就可以用KMP,每次出现一个下标就说明出现了一个子串,我们就使答案+1,这样求子串的时间复杂度是o(n+m)时间复杂度是o(q*(n+m))也是可以过的。
#include<iostream>
#include<cstdio>
#include<string>
#include<math.h>
using namespace std;
const int maxn=1005;
char s[maxn],t[maxn];
int slen,tlen;
int next[maxn];
void getnext()
{
int j,k;
j=0;k=-1;next[0]=-1;
while(j<tlen)
if(k==-1||t[j]==t[k])
next[++j]=++k;
else
k=next[k];
}
int kmp_count(int m,int n)
{
int ans=0;
int i,j=0;
if(slen==1&&tlen==1)
{
if(s[0]==t[0])
return 1;
else
return 0;
}
getnext();
for(int i=m;i<n;i++)
{
while(j>0&&s[i]!=t[j])
j=next[j];
if(s[i]==t[j])
j++;
if(j==tlen)
{
ans++;
j=next[j];
}
}return ans;
}
int main()
{
int q;
while(scanf("%d%d%d",&slen,&tlen,&q)!=EOF)
{
scanf("%s",s);
scanf("%s",t);
while(q--)
{
int m,n;
scanf("%d%d",&m,&n);
printf("%d\n",kmp_count(m-1,n));
}
}
return 0;
}
总结
一个题的四种解法。