题目链接:https://vjudge.net/contest/362409#problem/I
题意:给定一个字符串s,求有多少子串,满足长度为M*L,且由M个不同的子串(长度均为L)组成。
思路:
先用hs[i]记录前i个字符的hash值,然后利用hs[r]-hs[l-1]*base[r-l+1]得到子串[l,r]的hash值。(自然溢出可AC,模1e18的质数过不了)
利用上面的方法可以O(1)得到子串[l,r]的hash值。然后就是遍历,以i(1<=i<=L)为起点,将i后面的M个长度为L的子串放进map里判重,然后通过删除第一个,添加后面一个来移动。删除时如果map值为0,要将其从map中删除。复杂度为O(L*n/L)=O(n)。
AC code:
#include<cstdio> #include<algorithm> #include<cstring> #include<tr1/unordered_map> using namespace std; using namespace tr1; typedef unsigned long long ull; const int maxn=1e5+5; int n,M,L,ans; char s[maxn]; ull base=131,bs[maxn],hs[maxn]; unordered_map<ull,int> mp; ull gethash(int l,int r){ return (hs[r]-hs[l-1]*bs[r-l+1]); } void init(){ bs[0]=1; for(int i=1;i<maxn;++i) bs[i]=bs[i-1]*base; } int main(){ init(); while(~scanf("%d%d",&M,&L)){ scanf("%s",s+1); n=strlen(s+1); ans=0; hs[0]=0; for(int i=1;i<=n;++i) hs[i]=(hs[i-1]*base+s[i]); for(int i=1;i<=L;++i){ mp.clear(); int p=i,num=0; while(p+L<=n+1){ ull t1=gethash(p,p+L-1); ++mp[t1]; ++num; if(num==M) break; p+=L; } if(num!=M) break; if(mp.size()==M) ++ans; p+=L; while(p+L<=n+1){ ull t2=gethash(p-M*L,p-M*L+L-1); ull t3=gethash(p,p+L-1); ++mp[t3]; --mp[t2]; if(mp[t2]==0) mp.erase(t2); if(mp.size()==M) ++ans; p+=L; } } printf("%d ",ans); } return 0; }