【HDU 4821 String】 Hash+尺取

HDU4821
本题题意就是给你一个字符串,问你该字符串具有多少个子串满足
长度为m*l,而且可以拆成m个长度为l的不相同子串。
定长字符串问题,很明显是可以hash+尺取的,我们可以枚举l个子串的起点,之后算出当前m*l的字符串的情况,之后不断往后尺取就可以了
复杂度是 o ( l ( l e n / l ) l o g ( m ) ) 因为尺取的过程每次向后跳l个字符,log是map带来的所以复杂度是可以的
然而如果枚举所有起点的m*l字符串验证复杂度就会变成 o ( l e n l 是会超时的
这就是利用hash+尺取带来的优势。
HDU4821代码

#include<stdio.h>
#include<iostream>
#include<string.h>
#include<map>
using namespace std;
typedef unsigned long long ull;
#define dbg(x) cout<<#x<<" = "<<x<<endl;
#define Get_hash(i,L) hash_[i]-hash_[i+L]*xp[L]
const int maxn = 1e5+5;
ull xp[maxn],hash_[maxn];
ull x[maxn];
char str[maxn];
void init()
{
    xp[0]=1;
    for(int i=1;i<maxn;i++)
        xp[i]=xp[i-1]*13331;
    return ;
}
int Make_hash(char str[])
{
    int len=strlen(str);
    hash_[len]=0;
    for(int i=len-1;i>=0;i--)
        hash_[i]=hash_[i+1]*13331+(str[i]-'a'+1);
    return len;
}
map<ull,int> mp;
int main()
{
  init();
  int m,l;
  while(scanf("%d%d",&m,&l)!=EOF)
  {
      int cnt=0;
      scanf("%s",str);
      int len=Make_hash(str);
      for(int i=0;i<len-l+1;i++)
      {
          x[i]=Get_hash(i,l);
      }
      for(int i=0;i<l&&i+m*l<=len;i++)//枚举起点,注意范围
      {
          mp.clear();
          int ans=0;
          for(int j=i;j<i+m*l;j+=l)
          {
              if(mp[x[j]]==0)
              {
                  ans++;
              }
              mp[x[j]]++;
          }
          if(ans==m)  cnt++;//以上是用来计算出第一个完整的m*l块的情况,之后尺取就可以了
          for(int j=i+m*l;j+l<=len;j+=l)//注意范围
          {
              ull tmp=x[j-m*l];
              mp[tmp]--;//去掉当前第一个长度为l的子串
              if(mp[tmp]==0) ans--;
              tmp=x[j];//加上当前长度l的子串
              if(mp[tmp]==0)
              {
                  ans++;
              }
              mp[tmp]++;
              if(ans==m) cnt++;//当前的m个均不相同
          }
      }
      printf("%d\n",cnt);
  }
  return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_38891827/article/details/80723640