单词背诵
题目描述
灵梦有n个单词想要背,但她想通过一篇文章中的一段来记住这些单词。
文章由m个单词构成,她想在文章中找出连续的一段,其中包含最多的她想要背的单词(重复的只算一个)。并且在背诵的单词量尽量多的情况下,还要使选出的文章段落尽量短,这样她就可以用尽量短的时间学习尽可能多的单词了。
输入格式
第1行一个数n,
接下来n行每行是一个长度不超过10的字符串,表示一个要背的单词。
接着是一个数m,
然后是m行长度不超过10的字符串,每个表示文章中的一个单词。
输出格式
输出文件共2行。第1行为文章中最多包含的要背的单词数,第2行表示在文章中包含最多要背单词的最短的连续段的长度。
输入输出样例
3 hot dog milk 5 hot dog dog milk hot
3 3
说明/提示
【数据范围】
对于30%的数据 n<=50,m<=500;
对于60%的数据 n<=300,m<=5000;
对于100%的数据 n<=1000,m<=100000;
题目意思:
给出两个单词集。
1.问集合1中的单词,有多少个在集合2中出现。
2.求在满足第一问的最多出现次数的情况下,集合2中最短的连续子集。
分析:
由于字符处理起来不是很方便,因此我们使用哈希将字符串转化成数字,这样处理起来会比字符串方便的多。
当然也可以使用 map 来存。
对于第一问比较简单。
用一个bool 数组v1[] 0/1 记录这个单词是否在集合1中出现过(1:是,0:否);
然后对集合2进行判断。
难点在第二问。
n的规模比较大,可以想到基本是需要线性的了。
我提供一种单调队列做法。
首先申明几个变量:
1. lst [ 哈希值(s) ] 表示处理到当前字符串,字符串s上一次出现的位置在哪里。
2. num 计数器,表示处理到当前字符串,有贡献的字符串个数,即为出现在集合1里的字符串个数(相同算一次)。
3. v2[ 哈希值(s) ] 表示s是否出现在集合1和集合2中,若没有,则无需考虑这个字符串。
可以明确得到,当num=ans1时,更新一次答案。
接下来最重要的就是单调队列里面放什么?
我们放进去的是每个有贡献的字符串 b[i] 。
满足单调性的是他们的位置。
如果下一个相同的字符串出现了,就可以把它弹掉了。
这样当我们统计答案时(num==ans1)
我们用当前位置 i 减去队列首元素的位置+1,就是这一段满足条件区间长度。
最后特判一下0.
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int N=1e6,P=1e7+9,inf=1e9;
int n,m,ans1,ans2=inf;
int a[N],b[N];
bool v1[P+1],v2[P+1],vis[P+1];
int lst[P],q[N];
char s[20];
inline int hash(char *s){//哈希函数
int len=strlen(s);
long long res=0;
for(int i=0;i<len;i++){
res*=31;
res+=s[i]-'a';
res%=P;
}
return res;
}
int main()
{
int i,j;
scanf("%d",&n);
for(i=1;i<=n;i++){
scanf("%s",s);
a[i]=hash(s);
v1[a[i]]=1;//v1[] 记录是否在集合1中出现
}
scanf("%d",&m);
for(i=1;i<=m;i++){
scanf("%s",s);
b[i]=hash(s);
if(v1[b[i]]){
if(!v2[b[i]]){
v2[b[i]]=1;
ans1++;
}
}
}
int num=0,head=1,tail=0;
for(i=1;i<=m;i++){
if(lst[b[i]]==0&&v2[b[i]]) num++;
if(v2[b[i]]){//如果!v2[]一定不会有贡献
lst[b[i]]=i;
q[++tail]=i;
}
while(head<=tail&&q[head]<lst[b[q[head]]]) head++;
if(num==ans1) ans2=min(ans2,i-q[head]+1);//更新答案
}
if(ans1) printf("%d\n%d",ans1,ans2);
else printf("0\n0");
}