一中 2.20 哈希专题 题解

来源:互联网 发布:微博淘宝企业版 编辑:程序博客网 时间:2024/05/17 01:53
 PS:已经很长时间没有发题解了,说明我的做题之旅并不顺利。总算把昨天哈希专题的四道题做完了,来小结一下。
 

1.给1定一个字符串S1~Sn,给定一个匹配串 s1~sm,求有多少匹配子串。(N<=5000000,M<=50)

据说,这题暴力都能过,我还是练了一下哈希。
真的是裸的哈希,但是我因为我初学,连双关键字都不想编,还是试了很多取模的值。

(PS.我认为这个双关键字并不是:a1=now%p1;a2=now%p2,然后对hash[a1*p1+a2](其中p1,p2是取模的数)进行处理。这样说白了还是单数组,难以去重。真正有效的是开两个两维数组,对于每个hash[now],可能有不同的值来得出的,因此我们把每个值都记录一下。但是怎么判断他们是否是同一个值呢?就是在计算呢now时,如法制炮的计算一个now2,并在hash2的数组中巴每种值得now2记录一下,最后再判断。)

代码:
#include<stdio.h>using namespace std;const long mod=9797797;long hash[10000008];long i,m,n;char a[5000001],b[51];int main(){  freopen("str.in","r",stdin);freopen("str.out","w",stdout);  scanf("%ld%ld\n",&n,&m);m--;n--;  scanf("%s\n",a);scanf("%s",b);  long chen=1;long now=0,now2=0;  for (i=m;i>=0;i--)   {    long the=(chen*(a[i]-'a'))%mod;    long the2=(chen*(b[i]-'a'))%mod;    now=(now+the)%mod;    now2=(now2+the2)%mod;    if (i>0) chen=chen*26%mod;  }  hash[now]++;  for (i=m+1;i<=n;i++)  {    now=(now+mod-(a[i-m-1]-'a')*chen%mod)%mod;    now=now*26%mod;    now=(now+a[i]-'a')%mod;    hash[now]++;  }  printf("%ld",hash[now2]);  return 0;} 




2.现在给定了N个RZZ 的基因和 M 个YZH 的基因,要求找出每一个 YZH基因与多少个RZZ 基因相匹配。 基因匹配需要满足以下条件:它们的最长前缀的长度等于两者中较短者的长度。(N,M<=5万)

我们可以根据N个RZZ的基因构建一个字母树。(忘了说了,基因是由0和1组成的)同时,在每个基因的结尾标记一下。
构建完后,我们把M个YZH的基因一一带进去得解。
对于每个YZH的基因,分3种情况讨论:
(1)它到字母树的某处时突然没了,此时那个结点并不是叶子节点。
处理:ans=该节点祖先中所有结尾个数+
该节点孩子中所有的结尾个数+该节点所有的结尾个数(一般是1,万一数据坑)
(2)它到字母树的某结点时,下一个字符是0(或1),而该节点的孩子只有1(或0)。
处理:ans=
该节点祖先中所有结尾个数+该节点所有的结尾个数
(3) 
它到字母树的某结点时,该节点已经没有孩子了。
处理:
ans=该节点祖先中所有结尾个数+该节点所有的结尾个数
我按这个编了,结果超时了部分点。


超时代码:

#include<stdio.h>using namespace std;long left[8000001],right[8000001],f[8000001],cnt,n,m,i,j,t,k,x,ans;bool flag;void go(long k){  ans+=f[k];  if (left[k]!=0) go(left[k]);  if (right[k]!=0) go(right[k]);}int main(){  freopen("orzrzz.in","r",stdin);freopen("orzrzz.out","w",stdout);  scanf("%ld%ld",&n,&m);cnt=1;  for (i=1;i<=n;i++)  {    scanf("%ld",&t);k=1;    for (j=1;j<=t;j++)     {      scanf("%ld",&x);      if (x==0){if (left[k]==0) left[k]=++cnt;k=left[k];}      if (x==1){if (right[k]==0) right[k]=++cnt;k=right[k];}    }    f[k]++;  }  for (i=1;i<=m;i++)  {    scanf("%ld",&t);k=1;ans=0;flag=false;    for (j=1;j<=t;j++)    {      scanf("%ld",&x);      if (!flag) ans+=f[k];      if (x==0&&!flag){if (left[k]==0) flag=true;k=left[k];}      if (x==1&&!flag){if (right[k]==0) flag=true;k=right[k];}    }    if (!flag) {go(k);}    printf("%ld\n",ans);  }  return 0;}



后经过HHD大牛的改进,总算A了。因为在求每个点的孩子结点时,要遍历多次,对于每条基因,最坏要遍历n次!
我们在每个结点时开一个down数组,在构建字母树时预处理。
 

AC代码:
#include<stdio.h>using namespace std;long left[1000001],right[1000001],f[1000001],down[1000001],cnt,n,m,i,j,t,k,x,ans;bool flag;int main(){  freopen("orzrzz.in","r",stdin);freopen("orzrzz.out","w",stdout);  scanf("%ld%ld",&n,&m);cnt=1;down[1]=0;  for (i=1;i<=n;i++)  {    scanf("%ld",&t);k=1;down[1]++;    for (j=1;j<=t;j++)     {      scanf("%ld",&x);      if (x==0){if (left[k]==0) left[k]=++cnt;k=left[k];down[k]++;}      if (x==1){if (right[k]==0) right[k]=++cnt;k=right[k];down[k]++;}    }    f[k]++;  }  for (i=1;i<=m;i++)  {    scanf("%ld",&t);k=1;ans=0;flag=false;    for (j=1;j<=t;j++)    {      scanf("%ld",&x);      if (!flag) ans+=f[k];      if (x==0&&!flag){if (left[k]==0) flag=true;k=left[k];}      if (x==1&&!flag){if (right[k]==0) flag=true;k=right[k];}    }    if (!flag) ans+=down[k];    printf("%ld\n",ans);  }  return 0;}


 
3.经过众蒟蒻研究,DJ 在讲课之前会有一个长度为 N方案,我们可以把它看作一个数列;同样,花神在听课之前也会有一个嘲讽方案,有 M个,每次会在 x 到 y 的这段时间开始嘲讽,为了减少题目难度,每次嘲讽方案的长度是一定的,为K。花神嘲讽DJ 让DJ 尴尬需要的条件:在x~y 的时间内 DJ 没有讲到花神的嘲讽方案, 即J 的讲课方案中的x~y 没有花神的嘲讽方案【这样花神会嘲讽J 不会所以不讲】 。经过众蒟蒻努力,在一次讲课之前得到了花神嘲讽的各次方案,DJ 得知了这个消息以后欣喜不已,DJ 想知道花神的每次嘲讽是否会让DJ 尴尬【说不出话来】 。
对于每一个嘲讽做出一个回答会尴尬输出‘Yes’ ,否则输出‘No’(N,M<=10万,k<=20) 

这道题开始时十分纠结。开哈希的话,这最多是10万进制;开字母树也存不下。

哈希挂链被卡代码几个点的代码:
(呵呵,常数太大了!!) 
 
#include<stdio.h>using namespace std;const long maxn=100000;const long size=200;long hash[maxn+1][size+1],cnt[maxn+1],a[21],n,m,k,x,i,j,y;bool ans;bool find(long now,long start){  if (now==k+1) return true;  if (start+k-now>y) return false;  bool flag=false;long i;  for (i=1;i<=cnt[a[now]];i++)   {    if (hash[a[now]][i]==start+1) flag=find(now+1,hash[a[now]][i]);    if (flag) return true;  }  return false;}int main(){  freopen("taunt.in","r",stdin);freopen("taunt.out","w",stdout);  scanf("%ld%ld%ld",&n,&m,&k);  for (i=1;i<=n;i++)  {    scanf("%ld",&x);    hash[x][++cnt[x]]=i;  }  for (i=1;i<=m;i++)  {    scanf("%ld%ld",&x,&y);    for (j=1;j<=k;j++) scanf("%ld",&a[j]);    ans=false;    if (y-x+1<k) ans=false;    else {           for (j=1;j<=cnt[a[1]];j++)             if (hash[a[1]][j]>=x)                {                 ans=find(2,hash[a[1]][j]);                 if (ans) break;               }         }    if (ans) printf("No\n");else printf("Yes\n");  }  return 0;}


后听说暴力有效O(∩_∩)O~~于是秒过。

AC代码:
#include<stdio.h>using namespace std;const long maxn=100000;const long size=200;long hash[maxn+1][size+1],cnt[maxn+1],a[21],n,m,k,x,i,j,y;bool ans;bool find(long now,long start){  if (now==k+1) return true;  if (start+k-now>y) return false;  bool flag=false;long i;  for (i=1;i<=cnt[a[now]];i++)   {    if (hash[a[now]][i]==start+1) flag=find(now+1,hash[a[now]][i]);    if (flag) return true;  }  return false;}int main(){  freopen("taunt.in","r",stdin);freopen("taunt.out","w",stdout);  scanf("%ld%ld%ld",&n,&m,&k);  for (i=1;i<=n;i++)  {    scanf("%ld",&x);    hash[x][++cnt[x]]=i;  }  for (i=1;i<=m;i++)  {    scanf("%ld%ld",&x,&y);    for (j=1;j<=k;j++) scanf("%ld",&a[j]);    ans=false;    if (y-x+1<k) ans=false;    else {           for (j=1;j<=cnt[a[1]];j++)             if (hash[a[1]][j]>=x)                {                 ans=find(2,hash[a[1]][j]);                 if (ans) break;               }         }    if (ans) printf("No\n");else printf("Yes\n");  }  return 0;}


  
4.
N(1<=N<=100000)头YZH,一共 K(1<=K<=30)种特色,每头YZH有多种特色, 用二进制01表示它的特色ID。 比如特色ID 为13(1101),则它有第1、3、4种特色。[i,j]段被称为balanced 当且仅当K 种特色在[i,j]内拥有次数相同。求最大的[i,j]段长度。 

明显,转化为二进制后要前缀和优化,但是光枚举的话也是n^2的效率。我们不由得想到了二分!!白高兴一场,这不是单调递增的效果,比如4满足答案,但3可以不满足。
后来WWT大神来了,把此题轻松秒杀。
我们设K=3,且设某两段前缀和为:
......a1......a2......
......b1......b2......
......c1......c2......
首先,如果a2-a1=b2-b1=c2-c1,那么这两段可以更新答案。
我们转化一下a2-b2=a1-b1,c也是同理。
也就是说,如果每组数同时减去a1后,如果所剩的数相同,就是可行的,
于是哈希横空出世!听说人家单关键字卡了半天,卡出了一个取模数p=1593573.
但是我觉得这样不厚道,于是开了个刚才说的
双关键字哈希。  

AC代码:
#include<stdio.h>const long l=4;const long l2=128;const long p=10017;const long q=1593573;long hash[p][201],hash2[p][201],cnt[p],a[31][100001];long n,k,i,j,now,two,chen,chen2,ans,x,kk;bool flag;using namespace std;int main(){  freopen("lineup.in","r",stdin);freopen("lineup.out","w",stdout);  scanf("%ld%ld",&n,&k);  for (i=1;i<=n;i++)  {    scanf("%ld",&x);kk=0;    while (x>0)    {      kk++;      a[kk][i]+=x&1;      x=x>>1;    }  }  for (i=1;i<=n;i++)  {    for (j=1;j<=k;j++)      a[j][i]+=a[j][i-1];  }  ans=0;  for (i=0;i<=n;i++)  {    chen=1;now=two=0;chen2=1;    for (j=1;j<=k;j++)    {      now=(now+(a[j][i]-a[k][i]+p)*chen)%p;      two=(two+(a[j][i]-a[k][i]+q)*chen2)%q;      chen=chen*l%p;chen2=chen2*l2%q;    }    flag=true;    for (j=1;j<=cnt[now];j++)      if (hash2[now][j]==two)      {        if (i-hash[now][j]>ans) ans=(i-hash[now][j]);        flag=false;      }    if (flag)    {      hash[now][++cnt[now]]=i;      hash2[now][cnt[now]]=two;    }  }  printf("%ld",ans);  return 0;}


 
题解完! 

1 0
原创粉丝点击