洛谷1381 单词背诵

P1381 单词背诵

题目描述

灵梦有n个单词想要背,但她想通过一篇文章中的一段来记住这些单词。

文章由m个单词构成,她想在文章中找出连续的一段,其中包含最多的她想要背的单词(重复的只算一个)。并且在背诵的单词量尽量多的情况下,还要使选出的文章段落尽量短,这样她就可以用尽量短的时间学习尽可能多的单词了。

输入输出格式

输入格式:

 

第1行一个数n,

接下来n行每行是一个长度不超过10的字符串,表示一个要背的单词。

接着是一个数m,

然后是m行长度不超过10的字符串,每个表示文章中的一个单词。

 

输出格式:

 

输出文件共2行。第1行为文章中最多包含的要背的单词数,第2行表示在文章中包含最多要背单词的最短的连续段的长度。

 

输入输出样例

输入样例#1:
3
hot
dog
milk
5
hot
dog
dog
milk
hot
输出样例#1:
3
3

说明

【数据范围】

对于30%的数据 n<=50,m<=500;

对于60%的数据 n<=300,m<=5000;

对于100%的数据 n<=1000,m<=100000;

 

这个题难了我好久

首先输入单词时把单词hash掉再存储,方便以后的操作,word[]是要背的单词,book1[]和book2[]都是课文中的单词,其中book1[]是从大到小排序的,book2[]是输入时的顺序

第一问好办,不说了

来说说第二问

题意是说找到连续的能包含所有在课文中和单词表中都有的单词的单词串最长的答案(我尽量说的清楚)

可以发现,这个问题是与“单调”联系的

搞一个头指针和尾指针,need是还需要寻找的单词数

如果l~r这串单词已经包含了所有要找的单词,那么更新答案,尾指针向前移动,此时再看看尾指针向前移动所抛弃的那个单词,是否是这段区间内独一无二的,如果既是独一无二的,又是我们要找的,那么need++

如果要找的单词还没找完,那就接着找呗,首指针前移,然后看看又纳入的这个单词是否是我们需要的,如果是,再看看这个单词是否是独一无二的,如果是,那么need--

直到最后一个单词讨论完毕,结束

看一下以上几段话在代码中的实现

while(1){
        if(need==0){
            ans=min(ans,r-l);//更新答案 
            l++;//尾指针向前移动 
            pos=find_pos(1,n,book2[l]);//找到目前尾指针所指单词在单词表中的位置 
            if(pos!=0){b[pos]--;if(b[pos]==0)need++;}
            //如果单词表中有这个单词,这个单词在l~r中出现的次数--,如果删之前这个单词在l~r范围的课文里只出现过那一次,还需要寻找的单词数+1 
        }
        else{
            if(r==m)break;//已经检查完所有的单词,退出循环 
            r++;
            pos=find_pos(1,n,book2[r]);//pos是这个单词在单词表中的位置 
            if(pos!=0){b[pos]++;if(b[pos]==1)need--;}
            //如果单词表中有这个单词,这个单词在l~r中出现的次数++,如果在l~r范围的课文里这个单词是第一次出现,还需要寻找的单词数-1 
        }
    }

下面是完整代码

我觉得看代码的话,解释太多特别恶心,所以来段干净的

#include<iostream> 
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
int book1[100010],book2[100010],word[1010];
int sugar[13]={13,1313,131313,131313,11113333,13331133,1313133,33331113,100303433,23333,1233323,17171717};
int n,m;
int b[100010];
char s[13];
int Hash(char a[]){
    int len=strlen(a);
    int result=0;
    for(int i=len-1;i>=0;i--){
        result+=((a[i]-'a'+1)*sugar[i])%100000007;
    }
    return result;
}
void init(){
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>s;
        word[i]=Hash(s);
    }
    scanf("%d",&m);
    for(int i=1;i<=m;i++){
        cin>>s;
        book1[i]=Hash(s);
        book2[i]=book1[i];
    }
    sort(book1+1,book1+m+1);
}
bool find_word(int l,int r,int k){
    int mid=(l+r)>>1;
    if(book1[mid]==k)return 1;
    if(l==r)return false;
    if(k<book1[mid])return find_word(l,mid,k);
    else return find_word(mid+1,r,k);
}
int find_pos(int l,int r,int k){
    int mid=(l+r)>>1;
    if(word[mid]==k)return mid;
    if(l==r)return 0;
    if(k<word[mid])return find_pos(l,mid,k);
    else return find_pos(mid+1,r,k);
}
int main(){
    init();
    int tot=0;
    for(int i=1;i<=n;i++)if(find_word(1,m,word[i]))tot++;
    if(tot==0){cout<<0<<endl<<0;return 0;}
    cout<<tot<<endl;
    sort(word+1,word+n+1);
    int l=0,r=0,ans=m;int pos;
    int need=tot;
    while(1){
        if(need==0){
            ans=min(ans,r-l); 
            l++; 
            pos=find_pos(1,n,book2[l]); 
            if(pos!=0){b[pos]--;if(b[pos]==0)need++;}
        }
        else{
            if(r==m)break; 
            r++; 
            pos=find_pos(1,n,book2[r]);
            if(pos!=0){b[pos]++;if(b[pos]==1)need--;}
        }
    }
    cout<<ans;
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
根据提供的引用内容,P1381题目是关于单词背诵的。题目要求在一篇文章中找出一个连续的段落,其中包含最多的要背诵单词(重复的只算一个),并且在背诵单词量尽量多的情况下,还要使选出的文章段落尽量短,这样可以用尽量短的时间学习尽可能多的单词。 以下是使用C++的双指针(尺取法)解题思路: ```cpp #include <iostream> #include <unordered_map> #include <vector> using namespace std; int main() { int n, m; cin >> n >> m; vector<string> words(n); unordered_map<string, int> wordCount; for (int i = 0; i < n; i++) { cin >> words[i]; wordCount[words[i]]++; } int left = 0, right = 0; // 左右指针 int maxCount = 0; // 最多的目标单词个数 int minLength = n; // 最短长度 unordered_map<string, int> window; // 当前区间内的单词计数 while (right < n) { window[words[right]]++; if (window.size() <= m) { // 当前区间内的单词种类不超过m if (window.size() == m) { // 当前区间内的单词种类等于m,更新最多的目标单词个数和最短长度 int count = 0; for (auto it : window) { count += min(it.second, wordCount[it.first]); } if (count > maxCount || (count == maxCount && right - left + 1 < minLength)) { maxCount = count; minLength = right - left + 1; } } right++; } else { // 当前区间内的单词种类超过m,左指针右移 window[words[left]]--; if (window[words[left]] == 0) { window.erase(words[left]); } left++; } } cout << minLength << endl; return 0; } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值