题解 P1381 单词背诵(哈希,队列)
Posted wxy-max
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了题解 P1381 单词背诵(哈希,队列)相关的知识,希望对你有一定的参考价值。
第一问(哈希)
要使得包含单词最多,那么就需将文章中所有在单词表中出现过的单词全部统计。
而一个字符一个字符的比对复杂度过高,因此我们需要求出单词表与文章的字符串哈希,这样我们就可以(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 单词背诵(哈希,队列)的主要内容,如果未能解决你的问题,请参考以下文章