Live2D

单词背诵

原题链接

题目大意:

给出n个单词和有m个单词的文章,问其中包含这n个单词的数量(重复算一个),和包含最多单词数量下的最短连续段。
n<=1000,m<=100000
 

简略思路

首先,想到的肯定是hash+暴力
时间复杂度O(m²)
仔细读题,
假设右边界从右往左移动,当一个右边界对应的最小左边界找到后,再向左移动右边界,相应左边界不可能向右移动。以此可以去掉许多多余的循环。
双指针扫一扫就行了
 
已经懂得大佬默默离场
 
具体来说,

题目要求

给定n个单词和一个长为m的段落,要求在该段落内找出包含给定单词种类数最多且最短的最优段落,求该段落包含给定单词的种类数和段落长度。

思路分析

为了方便叙述,我们约定用数组a[ ]来存储给定的n个单词(将其称为目标单词),用数组b[ ]来按照顺序存储给定的m段内的m个单词。

初步思路:

大致思路应该是很好想的,差不多就是对段落整体扫描一遍来求最优段落。首先用Hash来记录n个给定单词和m个段落内单词,然后对比得出给定段落内包含了多少种目标单词,最后对段落整体扫描一遍来寻得最优段落。

那么重点是,如何对段落整体扫描呢?

我们在Hash时首先对段落扫描做好预先处理:对a[ ]内单词Hash时记录a[ ]的Hash值,为了方便与b[ ]比较,我们将其用bool数组存储(这样就相当于用数组来表示该Hash值是否出现过)。如果这个地方没看懂的话,去看一下下面的代码应该就理解了。对b[ ]内单词Hash时,我们用vis[]数组来对其是否为目标单词做标记,如果该单词的Hash值在a[ ]中已经出现过,说明该单词是目标单词之一,则将其对应的vis[ ]标记为1,同时用cnt记录出现的目标单词的种类数。

完成了预先处理,接下来我们来讨论如何对段落进行扫描求解最优段落。

我们采用枚举区间左右端点的方法来对段落进行扫描。这里我是从最右端向左开始枚举区间。如果觉得从左开始枚举更好理解的话,翻到我的题解底部~

我们首先对左端点进行移动。移动左端点的同时,我们使用appear[]记录一下目标单词在当前l,r包含的段落内出现的次数。如果该单词是初次出现,那么cnt--(或者再重新定义一个变量,出现一种++一下,最后比较是否和cnt相等也可),同时appear[ ]++(不是初次出现也要加,键见代码)。如果cnt=0了,那么说明当前我们枚举的段落已经包含了所有种类的b[ ]内目标单词。这时首先我们更新ans值来存储当前的最优解,然后对右端点进行操作。然后如果当前我们的右端点上的单词不是目标单词,那么我们就把它舍去,即r--;如果右端点上的单词在当前枚举段落内已经重复出现过,那么我们也可将其舍去。如果右端点上的单词是目标单词且仅出现一次,那么我们把它舍去的时候就再将cnt++,然后进行下一次扫描。这个地方是最难理解的,如果想不明白的话手动模拟一下。

最后附上代码(已包含注解):

#include<algorithm>
#include<iostream>
#include<iomanip>
#include<cstring>
#include<cstdio>
#include<cmath>
using namespace std;
typedef long long ll;
const int p=909043; 
int base=313;
int n,m,cnt,ans=1<<30,l,r;
char s[33];
int a[100003],b[100003];
int appear[5000003],vis[5000003],pre[5000003];
//pre[]记录灵梦要背的单词
//vis[]记录灵梦要背的单词在给出的段落中是否出现
//appear[]记录要背的单词在目前搜索到的段落中出现的次数 
int hashs(char qwq[])
{
    int len=strlen(qwq);
    int sum=0;
    for(int i=0;i<len;i++)
    {
        sum=(sum*base+qwq[i])%p;
    }
    return sum;
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        cin>>s;
        a[i]=hashs(s);
        pre[a[i]]=1;
    }
    scanf("%d",&m);
    for(int i=1;i<=m;i++)
    {
        cin>>s;
        b[i]=hashs(s);
        if(pre[b[i]]&&!vis[b[i]])
        {
            vis[b[i]]=1;
            cnt++;
        }
    }
    if(cnt==0)
    {
        printf("0\n0");//一定要记得讨论这种情况QAQQ 
        return 0;
    }
    else printf("%d\n",cnt);
    l=m;//从右向左扫 
    r=m;
    while(true)
    {
        if(!cnt)//cnt==0时对答案进行更新 
        {
            while(!vis[b[r]]) r--;
            //如果区间右端点不是灵梦要背的单词就将段落长度缩小 
            ans=min(ans,r-l);//想想为什么不是(r-l+1)呀qwq    
            if(appear[b[r]]>=1) 
            {
                if(appear[b[r]]==1)
                {
                    cnt++;
                }
                appear[b[r]]--;
                r--;
            }
            //如果右端点的单词已经在段落里出现过了,就可以缩小段落长度
            //如果右端点的单词只出现了一次,那么我们将右端点向左移的同时要cnt++(因为这种单词在当前枚举段落内已经不再出现啦) 
        }
        else
        {
            if(l==0) break; 
            if(vis[b[l]])
            {
                if(!appear[b[l]])//如果这种单词在当前枚举的段落里初次出现,那么就说明当前段落内的种类数加一 
                {
                    cnt--;
                }
                appear[b[l]]++;//该种单词出现的次数增加 
            }
            l--;
        }
    }
    printf("%d",ans);
    return 0;
}

 

 
 
 
posted @ 2019-02-20 15:08  γひん0ΖΖƦ  阅读(189)  评论(1编辑  收藏  举报