码迷,mamicode.com
首页 > 其他好文 > 详细

题解 P1381 【单词背诵】(哈希,队列)

时间:2020-07-05 10:44:50      阅读:57      评论:0      收藏:0      [点我收藏+]

标签:因此   front   char*   cstring   复杂度   为我   check   log   元素   

第一问(哈希)

要使得包含单词最多,那么就需将文章中所有在单词表中出现过的单词全部统计。

而一个字符一个字符的比对复杂度过高,因此我们需要求出单词表与文章的字符串哈希,这样我们就可以\(O(1)\)比较了。而哈希之后的值是一个整数,那么我们目前的问题就转化为有两个数组\(A[]\)\(B[]\)\(A[]\)数组中有多少个不同的数在\(B[]\)中出现过。那么对于这个问题很显然我们可以建立一个\(bool\)数组\(C[]\)\(bool\)数组\(used[]\),扫 一遍\(A[]\),以\(A[]\)中的每个元素为下标,赋值为\(true\)。扫一遍\(B[]\),如果\(C[B[i]]==true\)\(used[B[i]]=false\)那么\(cnt++\)\(used[B[i]]\)赋值为\(true\)

那么很显然我们就可以用哈希值作为\(C[]\)数组的下标(要保证在求哈希之模的数能作为数组下标)愉快的解决第一个问。

    cin >> n;
    for (int i = 0; i < n; i++){
        cin >> s;
        have[hsh(s)] = true;
    }
    cin >> m;
    for (int i = 1; i <= m; i++){
        cin >> s;
        val[i] = hsh(s);
        if (have[val[i]] && !used[val[i]]){
            cnt++;
            used[val[i]] = true;
        }
    }

此步复杂度为\(O(m)\)

第二问

第二问暴力部分分

\(O(m^3)\)暴力枚举

我们可以枚举长度\(len\)并分别判断从第\(i\)个单词开始为第一个单词的长度为\(len\)子串是否合法(即在\(B[]\)中存在连续的长度为\(k\)的一段,包含\(cnt\)个单词)。其中\(1≤len≤m\),判断的复杂度为\(O(m^2)\)

因此总复杂度为\(O(m^3)\)

\(O(m^2\ log\ m)\)暴力二分

在上面所描述的暴力中,我们将第二问转化为了一个判定问题。

而在我们所枚举的集合很显然是具有单调性的:

如果答案为\(k\)

那么如果长度大于\(k\)必然合法

而长度要是小于\(k\)则必然不合法

因此我们可以二分长度\(len\)\(check\)的复杂度为\(O(m^2)\),二分的复杂度为\(O(log\ m)\)因此总复杂度为\(O(m^2\ log\ m)\)

\(O(m)\)正解

\(f_{l,r}\)为从\(B[l]\)\(B[r]\)这段区间内,包含\(A[]\)中的数的个数。

\(f_{l,r}=cnt\)\(C[B[r]]=true\)并满足于\(C[B[r+1]]=false\)

设其中包含\(cnt\)\(A[]\)中元素个数的最短区间为\([a,b]\)(等价于\([a,r]\)

那么显然\(f_{a,b}=f_{l,r}=f_{a,r}\)

那么我们则可以用\(r - a + 1\)更新元素

我们维护一个集合\(S\)\(S‘\)\(S\)包括\(B[l] \sim B[r]\)\(S‘\)包括\(B[a] \sim B[r]\)

那么\((r-l+1)=|S|\)\((r-a+1)=|S‘|\)

我们已知\(l,r\)与集合\(S\),接下来即求出\(a\)\(S‘\)

因为\(f_{l,r}=f_{a,r}=cnt\)\([a,r]\)为包含\(cnt\)\(A[]\)中元素个数的最短区间

那么\(f_{a+1,r}\)必然小于\(cnt\)

\(f_{l,r-1}\)必然小于\(cnt\)

\([a,r]\)为连续的一段

因此我们得到\(S‘\)最朴素的做法即从\(l\)开始依次删除\(B[i]\)所对应的集合的元素。对应的区间为\([c,r]\)\(f_{c,r}<cnt\)\(r-c+2\)即为\(r - a + 1\)

因此我们可以枚举\(r\),计算\(f_{1,r}\)并维护集合\(S\),每次枚举插入\(B[r]\)

\(f_{1,r}=cnt\)时依次删除前\(i\)个元素,直到\(f_{1,r}<cnt\)

并用\(|S‘|+1\)更新答案。

而对于集合\(S\),因为我们只在一端插入,一端删除,因此可以用队列维护。

而对于每个元素最多进队\(1\)次出队\(1\)次。

因此复杂度为\(O(m)\)

#include <iostream>
#include <cstring>
#include <queue>
using namespace std;
const int mod = 1000007;
const int base = 131;
bool have[mod], used[mod];
int val[100005],sum[mod];
char s[15];
queue<int> q;

int hsh(char* s){
    int ret = 0, len = strlen(s);
    for (int i = 0; i < len; i++){
        ret = (ret * base + s[i]) % mod;
    }
    return ret;
}

int main() {
    int n, m, cnt = 0, len, now;
	cin >> n;
    for (int i = 0; i < n; i++){
        cin >> s;
        have[hsh(s)] = true;
    }
    cin >> m;
    for (int i = 1; i <= m; i++){
        cin >> s;
        val[i] = hsh(s);
        if (have[val[i]] && !used[val[i]]){
            cnt++;
            used[val[i]] = true;
        }
    }
    if (cnt == 0){
        cout << 0 << endl;
        cout << 0 << endl;
    }else{
        len = m;
        now = 0;
        for (int i = 1; i <= m; i++){
            int queuelen = q.size();
            q.push(i);
            sum[val[i]]++;
            if (have[val[i]] && sum[val[i]] == 1) now++;
            if (now == cnt){
                while (now == cnt){
                    int v = q.front();
                    q.pop();
                    sum[val[v]]--;
                    if (sum[val[v]] == 0 && have[val[v]]) now--;
                }
                queuelen = q.size() + 1;
                len = min(len,queuelen);
            } 
        }
        cout << cnt << endl << len;
    }
    return 0;
}

题解 P1381 【单词背诵】(哈希,队列)

标签:因此   front   char*   cstring   复杂度   为我   check   log   元素   

原文地址:https://www.cnblogs.com/wxy-max/p/13237700.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!