HASH算法模板以及简单的入门题总结

Hash算法模板

//hash一般用来解决字符串判重/字符串匹配问题
//遇见不定长问题可通过二分+hash降低复杂度
//遇见定长字符串问题可通过尺取+hash来降低复杂度
//二维hash的时候尺取方法就是把之前不需要的都变为0再加上当前行,将匹配字符串整体下移,来验证hash值是否相等
//---------------------------------单Hash-----------------------
#include<string.h>
typedef unsigned long long ull;
const int maxn=1e5+5;
ull hash_[maxn],xp[maxn];
void init()
{
    xp[0]=1;
    for(int i=1;i<maxn;i++)
        xp[i]=xp[i-1]*13331;//这里13331玄学数字,大概可以随意换
    return ;
}
void make_hash(char str[])//处理出str的hash值
{
    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 ;
}
ull Get_hash(int i,int L)//得到起点为i,长度为L的子串的hash值
{
    return hash_[i]-hash_[i+L]*xp[L];
}
//---------------------------------双Hash-----------------------
ll hash_[2][maxn],xp[2][maxn];
const int Mod1 = 1000000007;
const int Mod2 = 19260817;
void init()
{
    xp[0][0]=1;
    for(int i=1;i<maxn;i++)
        xp[0][i]=xp[0][i-1]*13331%Mod1;
    xp[1][0]=1;
    for(int i=1;i<maxn;i++)
        xp[1][i]=xp[1][i-1]*1331%Mod2;
    return ;
}
void make_hash(char str[])
{
    int len=strlen(str);
    hash_[0][len]=0;
    hash_[1][len]=0;
    for(int i=len-1;i>=0;i--)
    {
        hash_[0][i]=(hash_[0][i+1]*13331%Mod1+str[i]-'A'+1)%Mod1;
        hash_[1][i]=(hash_[1][i+1]*1331%Mod2+str[i]-'A'+1)%Mod2;
    }
    return ;
}
pll Get_hash(int i,int L)//得到起点为i,长度为L的子串的hash值
{
    return pll((hash_[0][i]-hash_[0][i+L]*xp[0][L]%Mod1+Mod1)%Mod1,(hash_[1][i]-hash_[1][i+L]*xp[1][L]%Mod2+Mod2)%Mod2);
}
//---------------------------------多Hash-----------------------
//T是Hash次数
//hash_interval [a,b)
template <int T>
struct StringHash
{
  std::vector<std::vector<int>> ha, pw;
  std::vector<int> M;

  explicit StringHash(const std::string& s)
      : StringHash(std::vector<int>(s.begin(), s.end())) {}

  explicit StringHash(const std::vector<int>& vec)
  {
    pw = ha =
        std::vector<std::vector<int>>(T, std::vector<int>(vec.size() + 1));
    std::vector<int> C;
    for (int i = 0; i < T; i++)
    {
      pw[i][0] = 1;
      C.push_back(rand_int());
      M.push_back(rand_int());
    }
    for (int z = 0; z < T; z++)
    {
      for (size_t i = 0; i < vec.size(); ++i)
      {
        ha[z][i + 1] = (1LL * ha[z][i] * C[z] + vec[i]) % M[z],
                  pw[z][i + 1] = 1LL * pw[z][i] * C[z] % M[z];
      }
    }
  }

  // hash value of interval [a, b)
  std::vector<int> hash_interval(int a, int b)
  {
    std::vector<int> ret(T);
    for (int z = 0; z < T; z++)
    {
      ret[z] = (ha[z][b] - 1LL * ha[z][a] * pw[z][b - a] % M[z] + M[z]) % M[z];
    }
    return ret;
  }

  static int rand_int() 
  {
    static std::mt19937 gen((std::random_device())());
    static std::uniform_int_distribution<int> uid(1e8, 1e9);
    return uid(gen);
  }
};

Hash第一题

HDU1686
题意就是找A串在B串中的出现次数(可重叠)。其实就是一个kmp模板题,但是我们可以用hash很容易的解决
我们得到A串的hash值,然后在B中枚举起点,长度为lena的子串,检验hash值是否相同就可以了。
HDU1686代码

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<string.h>
using namespace std;
typedef unsigned long long ull;
const int maxn = 1e6+5;
ull xp[maxn],hash_1[maxn],hash_2[maxn];
void init()
{
    xp[0]=1;
    for(int i=1;i<maxn;i++)
        xp[i]=xp[i-1]*13331;
}
ull get_hash(int i,int L,ull hash_[])//get_hash(i,L)可以得到从位置i开始的,长度为L的子串的hash值.
{
    return hash_[i]-hash_[i+L]*xp[L];
}
int make_hash(char str[],ull hash_[])
{
    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;
}
char str[maxn],str2[maxn];
int main()
{
    init();
    int t;
    scanf("%d",&t);
    while(t--)
    {
        int ans=0;
        scanf("%s%s",str,str2);
        int len1=make_hash(str,hash_1);
        int len2=make_hash(str2,hash_2);
        ull tmp=get_hash(0,len1,hash_1);
        for(int i=0;i<len2-len1+1;i++)//注意枚举时的边界问题
        {
            if(get_hash(i,len1,hash_2)==tmp)
                ans++;
        }
        printf("%d\n",ans);
    }
    return 0;
}

Hash第二题

POJ2774
题意就是求两个串的最长公共子串,正常做法应该是后缀数组跑一下height数组,这里说一下hash的做法。
由于没有给定长度,要求长度,这时就要想是否具有二分的性质,发现答案是具有二分性质的,所以我们可以二分答案,然后把A串中所有出现过的hash值放进一个数组,sort一下,然后对于每个B串产生的hash用lower_bound查询是否出现过,若出现过则直接返回true.复杂度是 o ( l e n ∗ l o g ( l e n ) ∗ l o g ( l e n ) ) o(len*log(len)*log(len)) o(lenlog(len)log(len)),跑了1250ms,用map判断是否出现过就会超时,这暂且作为hash的一个优化方式吧。
POJ2774代码

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<string.h>
#include<map>
using namespace std;
typedef unsigned long long ull;
const int maxn = 1e6+5;
ull xp[maxn],hash_1[maxn],hash_2[maxn];
ull a[maxn];
int len1,len2;
void init()
{
    xp[0]=1;
    for(int i=1;i<maxn;i++)
        xp[i]=xp[i-1]*13331;
}
ull get_hash(int i,int L,ull hash_[])
{
    return hash_[i]-hash_[i+L]*xp[L];
}
int make_hash(char str[],ull hash_[])
{
    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;
}
char str[maxn],str2[maxn];
bool check(int L)
{
    int cnt=0;
    for(int i=0;i<len1-L+1;i++)
    {
        a[cnt++]=get_hash(i,L,hash_1);
    }
    sort(a,a+cnt);
    for(int i=0;i<len2-L+1;i++)
    {
        ull tmp=get_hash(i,L,hash_2);
        int pos=lower_bound(a,a+cnt,tmp)-a;
        if(a[pos]==tmp) return true;
    }
    return false;
}
int main()
{
    init();
    while( scanf("%s%s",str,str2)!=EOF)
    {
        len1=make_hash(str,hash_1);
        len2=make_hash(str2,hash_2);
        int l=0,r=min(len1,len2),mid;
        while(l<=r)
        {
            mid=(l+r)>>1;
            if(check(mid)) l=mid+1;
            else r=mid-1;
        }
        printf("%d\n",r);
    }
    return 0;
}

Hash第三题

POJ3261
本题就是求字符串中至少出现过k次的最长子串。
正解应该是后缀数组,二分长度之后在height上分块去做即可
Hash的做法也很明显,二分长度,之后去计算当前长度出现次数是否有超过k的子串就可以了
POJ3261代码

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<string.h>
#include<map>
using namespace std;
typedef unsigned long long ull;
const int maxn = 1e5+5;
ull xp[maxn],hash_[maxn];
ull a[maxn];
int len1,len2;
map<ull ,int> mp;
void init()
{
    xp[0]=1;
    for(int i=1;i<maxn;i++)
        xp[i]=xp[i-1]*13331;
}
ull get_hash(int i,int L)
{
    return hash_[i]-hash_[i+L]*xp[L];
}
void make_hash(ull str[],int len)
{
    hash_[len]=0;
    for(int i=len-1;i>=0;i--)
    {
        hash_[i]=hash_[i+1]*13331+str[i];
    }
}
ull str[maxn];
bool check(int L,int len,int k)
{
    mp.clear();
    for(int i=0;i<len-L+1;i++)
    {
        ull tmp=get_hash(i,L);
        mp[tmp]++;
        if(mp[tmp]>=k) return true;
    }
    return false;
}
int n,k;
int main()
{
    init();
    while( scanf("%d%d",&n,&k)!=EOF)
    {
        for(int i=0;i<n;i++) scanf("%llu",&str[i]);
        make_hash(str,n);
        int l=0,r=n,mid;
        while(l<=r)
        {
            mid=(l+r)>>1;
            if(check(mid,n,k)) l=mid+1;
            else r=mid-1;
        }
        printf("%d\n",r);
    }
    return 0;
}

Hash第四题

UVA-257
本题意义是给你一堆字符串,将其中具有两个长度超过3的回文串的字符串输出
要求这两个回文字符串不能完全覆盖,但是可以重合,而且这两个回文字符串不能相同。
由于要求长度大于3而且可以重合,我们只要求每个长度为3/4的回文串就可以了,当某个位置存在长度>=3的回文串,如果串长为奇数,那么就取3,否则取4,然后把遍历指针i往后挪一位(去掉覆盖的情况),之后用hash判重就可以了。
比如AAAA
我们找到第一个AAA 然后把i++,这样就避免了AAAA覆盖AAA的情况
因为我们只考虑长度为3/4的回文串,所以只考虑这一种覆盖情况即可。
求某个位置为中心回文串的长度时,可以用manacher算法来计算出每个位置的回文半径。
UVA-257代码

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<string.h>
#include<sstream>
#include<map>
using namespace std;
typedef unsigned long long ull;
const int maxn=110010;
char s[maxn];
map<ull,int> mp;
char Ma[maxn*2];
int Mp[maxn*2];
string line;
string str;
ull hash_[maxn],xp[maxn];
void init()
{
    xp[0]=1;
    for(int i=1;i<maxn;i++)
        xp[i]=xp[i-1]*13331;
    return ;
}
void 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 ;
}
ull Get_hash(int i,int L)
{
    return hash_[i]-hash_[i+L]*xp[L];
}
void Manacher(char s[],int len)
{
    int l=0;
    Ma[l++]='$';
        Ma[l++]='#';
        for(int i=0; i<len; i++)
        {
            Ma[l++]=s[i];
            Ma[l++]='#';
        }
        Ma[l]=0;
        int mx=0,id=0;
        for(int i=0; i<l; i++)
        {
            Mp[i]=mx>i?min(Mp[2*id-i],mx-i):1;
           while(Ma[i+Mp[i]]==Ma[i-Mp[i]])
                Mp[i]++;
            if(i+Mp[i]>mx)
            {
                mx=i+Mp[i];
                id=i;
            }
        }
}
int main()
{
    int ans=0;
    init();
    while(scanf("%s",s)!=EOF)
    {
        mp.clear();
        int len=strlen(s);
        make_hash(s);
        Manacher(s,len);//manacher之后,Mp[i]-1为i位置的回文半径
        int cnt=0;
        for(int i=0;i<2*len+2;i++)
        {
            if(Mp[i]-1>=3)
            {
                if(Mp[i]%2==1)//回文串为偶数,取长度四的回文串
                {
                    int st=(i-1)/2-2;
                    int le=4;
                    ull tmp=Get_hash(st,le);
                    mp[tmp]++;
                }
                else//回文串为奇数,取长度三的回文串
                {
                    int st=i/2-2;
                    int le=3;
                    ull tmp=Get_hash(st,le);
                    mp[tmp]++;
                }
                i++;//当前位置存在大于三的回文串,避免覆盖后移一位。
            }
        }
        if(mp.size()>=2) printf("%s\n",s);
    }
    return 0;
}

Hash第五题

UVA-11019
本体题意就是给你AB两个字符矩阵,问你B矩阵在A矩阵中的出现次数。
我们可以进行二维hash,其实就是把n个横向串连在一起hash。
注意判相等的时候,我们不断进行尺取+hash,尺取的过程,我们删除当前第一行的hash值加上最后一行的hash值,删除第一行的hash值直接删去就可以
例如
A A A AAA AAA
B B B BBB BBB
C C C CCC CCC
我们删去第一行的hash值 相当于把矩阵变成了
000 000 000
B B B BBB BBB
C C C CCC CCC
此时我们再添加最后一行
000 000 000
B B B BBB BBB
C C C CCC CCC
D D D DDD DDD
如果这时候的B矩阵是
B B B BBB BBB
C C C CCC CCC
D D D DDD DDD
这两个矩阵的hash值不同的,为了处理这种情况,我们把B矩阵相应的添加前几行
变成
000 000 000
B B B BBB BBB
C C C CCC CCC
D D D DDD DDD
这样再去匹配就可以了。
以上就是二维hash大概的处理方法(是我自己想的做法,如果有其他好的尺取方法欢迎指教
掌握了这个做法,我们就可以枚举矩阵的左上角,然后对于当前列数的矩阵从上向下进行尺取,hash判断就可以了。
UVA-11019代码

#include<stdio.h>
#include<algorithm>
#include<iostream>
using namespace std;
const int maxn = 1e3+5;
const int MAXN = 1e6+5;
typedef unsigned long long ull;
ull hash_[maxn][maxn],xp[MAXN];
char str[maxn][maxn];
char str2[maxn][maxn];
void init()
{
    xp[0]=1;
    for(int i=1;i<MAXN;i++)
    {
        xp[i]=xp[i-1]*13331;
    }
    return ;
}
ull Get_hash(int i,int j,int l)
{
    return hash_[i][j]-hash_[i][j+l]*xp[l];
}
int main()
{
    init();
    int t;
    scanf("%d",&t);
    while(t--)
    {
        int ans=0;
        int n,m,x,y;
        scanf("%d%d",&n,&m);
        for(int i=0;i<n;i++)
        {
            scanf("%s",str[i]);
        }
        for(int i=0;i<n;i++)
        {
            hash_[i][m]=0;
            for(int j=m-1;j>=0;j--)
            {
                hash_[i][j]=hash_[i][j+1]*13331+str[i][j]-'a'+1;//每一行分别处理hash值
            }
        }
        scanf("%d%d",&x,&y);
        for(int i=0;i<x;i++)
            scanf("%s",str2[i]);
        ull tmp=0;
        for(int i=x-1;i>=0;i--)
        {
            for(int j=y-1;j>=0;j--)
            {
                tmp=tmp*13331+str2[i][j]-'a'+1;//处理出匹配矩阵的hash值
            }
        }
        for(int i=0;i<=m-y;i++)//枚举横坐标起点
        {
            ull tt=tmp;
            ull tmp2=0;
            for(int j=x-1;j>=0;j--)
            {
                tmp2=tmp2*xp[y]+Get_hash(j,i,y);
            }
             if(tt==tmp2) ans++;
             for(int j=x;j<n;j++)
             {
                 tmp2-=Get_hash(j-x,i,y)*xp[(j-x)*y];//尺取过程去除第一行,也就是将第一行变为0
                 tmp2+=Get_hash(j,i,y)*xp[j*y];//加上最后一行
                 tt=tt*xp[y];//将匹配矩阵第一行添上0
                 if(tmp2==tt) ans++;
             }
        }
        printf("%d\n",ans);
    }
    return 0;
}

Hash第六题

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

Hash第七题

HDU2859
求最大对称子矩阵,对称是延对角线对称,由于本题给出的对角线不方便操作,我们将所有字符串逆置一下,就变成了好操作的对角线,然后我们对每一行每一列进行hash,dp的时候只要从左上角dp值一直减小到0,判断是否有len满足向上和向左的hash值相等。
HDU2859代码

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<string.h>
using namespace std;
typedef unsigned long long ull;
const int maxn = 1e3+5;
ull hash_1[maxn][maxn],hash_2[maxn][maxn],xp[maxn];
char str[maxn][maxn];
void init()
{
    xp[0]=1;
    for(int i=1;i<maxn;i++)
        xp[i]=xp[i-1]*13331;//这里13331玄学数字,大概可以随意换
    return ;
}
void make_hash(int n)//处理出str的hash值
{
    for(int i=0;i<n;i++)
    {
        hash_1[i][n]=0;
        for(int j=n-1;j>=0;j--)
        {
            hash_1[i][j]=hash_1[i][j+1]*13331+(str[i][j]-'A')+1;
        }
    }
    for(int i=0;i<n;i++)
    {
        hash_2[i][n]=0;
        for(int j=n-1;j>=0;j--)
        {
            hash_2[i][j]=hash_2[i][j+1]*13331+str[j][i]-'A'+1;
        }
    }
    return ;
}
ull Get_hash1(int x,int i,int L)//得到起点为i,长度为L的子串的hash值
{
    return hash_1[x][i]-hash_1[x][i+L]*xp[L];
}
ull Get_hash2(int x,int i,int L)//得到起点为i,长度为L的子串的hash值
{
    return hash_2[x][i]-hash_2[x][i+L]*xp[L];
}
int dp[maxn][maxn];
int main()
{
    int n;
    init();
    while(scanf("%d",&n)!=EOF)
    {
        if(n==0) break;
        for(int i=0;i<n;i++) scanf("%s",str[i]);
        for(int i=0;i<n;i++) strrev(str[i]);
        make_hash(n);
        for(int i=0;i<n;i++)
            for(int j=0;j<n;j++)
                dp[i][j]=1;
        int ans=1;
        for(int i=1;i<n;i++)
        {
            for(int j=1;j<n;j++)
            {
                 int tmp=dp[i-1][j-1];
                 for(int len=tmp;len>=0;len--)
                 {
                    ull tmp1=Get_hash1(i,j-len,len+1);
                    ull tmp2=Get_hash2(j,i-len,len+1);
                    if(tmp1==tmp2)
                    {
                        dp[i][j]=len+1;
                        break;
                    }
                 }
                 ans=max(ans,dp[i][j]);
            }
        }
        printf("%d\n",ans);
    }
    return 0;
}

Hash第八题

CodeforcesMail.Ru Cup 2018 Round 3 E

题意

给你一个01串s,一个字符串t,
0 可 以 映 射 成 r 0 , 1 可 以 映 射 成 r 1 0可以映射成r_0,1可以映射成r_1 0r01r1
问 有 多 少 组 r 0 , r 1 可 以 满 足 映 射 之 后 s = t 问有多少组r_0,r_1可以满足映射之后s=t r0,r1s=t
∣ s ∣ &lt; = 1 0 5 , ∣ t ∣ &lt; = 1 0 6 |s|&lt;=10^5,|t|&lt;=10^6 s<=105,t<=106

做法

我 们 设 置 s 串 中 0 的 个 数 为 n u m 0 , s 串 中 1 的 个 数 为 n u m 1 我们设置s串中0的个数为num_0,s串中1的个数为num_1 s0num0,s1num1
如 果 固 定 r 0 的 长 度 , 也 就 固 定 了 r 1 的 长 度 , 也 就 确 定 了 r 0 和 r 1 如果固定r_0的长度,也就固定了r_1的长度,也就确定了r_0和r_1 r0r1,r0r1
由 于 n u m 0 ∗ l e n ( r 0 ) + n u m 1 ∗ l e n ( r 1 ) = l e n ( t ) 由于num_0*len(r_0)+num_1*len(r_1)=len(t) num0len(r0)+num1len(r1)=len(t)
我 们 只 要 枚 举 l e n ( r 0 ) 就 可 以 我们只要枚举len(r_0)就可以 len(r0)
首 先 要 判 断 l e n 1 是 否 为 整 数 , 之 后 枚 举 s 串 用 h a s h 判 断 每 一 位 0 或 1 是 否 能 正 确 转 义 首先要判断len_1是否为整数,之后枚举s串用hash判断每一位0或1是否能正确转义 len1shash01
如 果 每 一 位 都 可 以 转 义 那 么 a n s + + , 注 意 这 里 h a s h 用 自 动 溢 出 会 被 卡 , 需 要 多 h a s h 如果每一位都可以转义那么ans++,注意这里hash用自动溢出会被卡,需要多hash ans++,hashhash
复 杂 度 分 析 ( 来 自 p l s ) 复杂度分析(来自pls) (pls)
枚 举 0 的 长 度 l e n 0 , 那 么 就 有 n / l e n 0 个 可 行 解 枚举0的长度len0,那么就有n/len0个可行解 0len0n/len0
每 个 可 行 解 要 做 n 次 h a s h 每个可行解要做n次hash nhash
对 于 l e n 0 &gt; n / 2 , 总 复 杂 度 就 是 O ( n 2 / l e n 0 ) ≈ O ( n ) 对于len0&gt;n/2,总复杂度就是O(n^2/len0) \approx O(n) len0>n/2,O(n2/len0)O(n)
代码

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<string.h>
using namespace std;
#define dbg2(x1,x2) cout<<#x1<<" = "<<x1<<" "<<#x2<<" = "<<x2<<endl
typedef unsigned long long ull;
const int maxn = 1e6+10;
char str[maxn],t[maxn];
template <int T>
struct StringHash
{
  std::vector<std::vector<int>> ha, pw;
  std::vector<int> M;

  explicit StringHash(const std::string& s)
      : StringHash(std::vector<int>(s.begin(), s.end())) {}

  explicit StringHash(const std::vector<int>& vec)
  {
    pw = ha =
        std::vector<std::vector<int>>(T, std::vector<int>(vec.size() + 1));
    std::vector<int> C;
    for (int i = 0; i < T; i++)
    {
      pw[i][0] = 1;
      C.push_back(rand_int());
      M.push_back(rand_int());
    }
    for (int z = 0; z < T; z++)
    {
      for (size_t i = 0; i < vec.size(); ++i)
      {
        ha[z][i + 1] = (1LL * ha[z][i] * C[z] + vec[i]) % M[z],
                  pw[z][i + 1] = 1LL * pw[z][i] * C[z] % M[z];
      }
    }
  }

  // hash value of interval [a, b)
  std::vector<int> hash_interval(int a, int b)
  {
    std::vector<int> ret(T);
    for (int z = 0; z < T; z++)
    {
      ret[z] = (ha[z][b] - 1LL * ha[z][a] * pw[z][b - a] % M[z] + M[z]) % M[z];
    }
    return ret;
  }

  static int rand_int() {
    static std::mt19937 gen((std::random_device())());
    static std::uniform_int_distribution<int> uid(1e8, 1e9);
    return uid(gen);
  }
};
int num[2];
int main()
{
    scanf("%s%s",str,t);
    StringHash<10> sh(t);
    int lens=strlen(str);
    int lent=strlen(t);
    int ans=0;
    for(int i=0;i<lens;i++)
    {
        if(str[i]=='0') num[0]++;
        else num[1]++;
    }
    for(int i=1;i*num[1]<lent;i++)
    {
        if(((lent-i*num[1])%num[0])!=0) continue;
        int len0=(lent-i*num[1])/num[0];
        int len1=i;
        vector<int> flag0,flag1;
        int f0=-1,f1=-1;
        int flag=0;
        int tmp=0;
        for(int j=0;j<lens;j++)
        {
            if(str[j]=='0')
            {
               if(f0==-1)
               {
                   f0=1;
                   flag0=sh.hash_interval(tmp,tmp+len0);
                   tmp+=len0;
               }
               else
               {
                    vector<int> tt=sh.hash_interval(tmp,tmp+len0);
                    if(tt!=flag0)
                    {
                        flag=1;
                        break;
                    }
                    else
                    {
                        tmp+=len0;
                    }
               }
            }
            else
            {
               if(f1==-1)
               {
                   f1=1;
                   flag1=sh.hash_interval(tmp,tmp+len1);
                   tmp+=len1;
               }
               else
               {
                    vector<int> tt=sh.hash_interval(tmp,tmp+len1);
                    if(tt!=flag1)
                    {
                        flag=1;
                        break;
                    }
                    else
                    {
                        tmp+=len1;
                    }
               }
            }
        }
        if(flag0!=flag1&&flag==0) ans++;
    }
    printf("%d\n",ans);
    return 0;
}

Hash第九题

题目链接

http://codeforces.com/contest/727/problem/E

题意

给你一个长度为 n × k n \times k n×k的环,环上每一个位置有一个字符。
现在给你 g g g个长度为 k k k的字符串,问是否可以在 g g g个字符串中找出 k k k个构成这个环。

做法

由于单个字符串长度已经确定,只需要枚举第一个串在环中的下标即可。
而且只需要枚举到 k k k,因为枚举 1 1 1 k + 1 k+1 k+1是一样的。
对于每个枚举的起始位置,只需要每 k k k个字符判断一下是不是可以在 g g g个字符串中得到,这个过程用Hash直接可以做。
枚举起点 k k k次,每次计算 n n n个位置,总复杂度是 O ( n ∗ k ) O(n*k) O(nk)
这题会卡掉自然溢出,所以要用多Hash,自己第一次手写这个东西,常数可能有点大。

代码

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<string.h>
#include<map>
#include<stack>
using namespace std;
typedef long long ll;
typedef pair <ll, ll> pll;
const int maxn = 5e6+5;
const int Mod=1000000007;
#define Se second
#define Fi first
#define pb push_back
char str[maxn];
char tmp[maxn];
ll hash_[2][maxn],xp[2][maxn];
const int Mod1 = 1000000007;
const int Mod2 = 19260817;
void init()
{
    xp[0][0]=1;
    for(int i=1;i<maxn;i++)
        xp[0][i]=xp[0][i-1]*13331%Mod1;
    xp[1][0]=1;
    for(int i=1;i<maxn;i++)
        xp[1][i]=xp[1][i-1]*1331%Mod2;
    return ;
}
pll make_hash(char str[])
{
    int len=strlen(str);
    hash_[0][len]=0;
    hash_[1][len]=0;
    for(int i=len-1;i>=0;i--)
    {
        hash_[0][i]=(hash_[0][i+1]*13331%Mod1+str[i]-'A'+1)%Mod1;
        hash_[1][i]=(hash_[1][i+1]*1331%Mod2+str[i]-'A'+1)%Mod2;
    }
    return pll((hash_[0][0]-hash_[0][len]*xp[0][len]%Mod1+Mod1)%Mod1,(hash_[1][0]-hash_[1][len]*xp[1][len]%Mod2+Mod2)%Mod2);
}
pll Get_hash(int i,int L)//得到起点为i,长度为L的子串的hash值
{
    return pll((hash_[0][i]-hash_[0][i+L]*xp[0][L]%Mod1+Mod1)%Mod1,(hash_[1][i]-hash_[1][i+L]*xp[1][L]%Mod2+Mod2)%Mod2);
}
pll H[maxn];
map<pll,int> mp;
map<pll,stack<int> > mp2;
vector<pll> ct;
int main()
{
    init();
    int n,k,m;
    scanf("%d%d",&n,&k);
    scanf("%s",str);
    for(int i=0;i<n*k;i++) str[i+n*k]=str[i];
    str[2*n*k]='\0';
    scanf("%d",&m);
    for(int i=1;i<=m;i++)
    {
        scanf("%s",tmp);
        H[i]=make_hash(tmp);
        mp[H[i]]++;
        mp2[H[i]].push(i);
    }
    make_hash(str);
    int len=strlen(str);
    for(int i=0;i<k;i++)
    {
        ct.clear();
        int tt=0;
        for(int j=i;j<i+len/2;j+=k)
        {
            pll tmp=Get_hash(j,k);
            if(!mp.count(tmp)||mp[tmp]==0)  tt=1;
            else
            {
                ct.pb(tmp);
                mp[tmp]--;
            }
        }
        if(tt==0)
        {
            puts("YES");
            for(int j=i;j<i+len/2;j+=k)
            {
                pll tmp=Get_hash(j,k);
                int st=mp2[tmp].top();
                mp2[tmp].pop();
                printf("%d ",st);
            }
            return 0;
        }
        for(int j=0;j<ct.size();j++) mp[ct[j]]++;
    }
    puts("NO");
    return 0;
}


未完待续…

  • 11
    点赞
  • 42
    收藏
    觉得还不错? 一键收藏
  • 13
    评论
评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值