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记录一下,最后再判断。)
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代码:
题解完!
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=该节点祖先中所有结尾个数 + 该节点所有的结尾个数
我按这个编了,结果超时了部分点。
超时代码:
后经过HHD大牛的改进,总算A了。因为在求每个点的孩子结点时,要遍历多次,对于每条基因,最坏要遍历n次!
我们在每个结点时开一个down数组,在构建字母树时预处理。
AC代码:
我们可以根据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数组,在构建字母树时预处理。
#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万进制;开字母树也存不下。
哈希挂链被卡代码几个点的代码:
(呵呵,常数太大了!!)
对于每一个嘲讽做出一个回答会尴尬输出‘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代码:
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;
}
题解完!