一中 2.20 哈希专题 题解

 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
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值