P1381 单词背诵
优美的尺取法!!!
这道题看到判断相同的字符串,用pp想都知道用hash。
其实重点在第二问:如何用较小的时间复杂度来实现题目中的最小距离包含最多要背的单词。
这个时候就需要找到一种算法,这种算法名字叫做尺取法。
从别人的博客偷来三张图:
算法核心思想就是上面写的那样:
-
初始化左顶点、右顶点和区间内还不满足要求的数目。
-
套一个
while(1)
的循环,这里从左端点开始枚举,所以当l或r等于m + 1
时自然退出。 -
当区间满足要求时,试着缩小左端点。
-
当区间不满足要求时,从右端点一个一个地扩展。
看不懂是正常的,你可以照着下面的代码打一遍,然后开debug看一下就知道了。
更新答案的时候应该更新\(r-l\),因为这个算法是左闭右开的。
复杂度十分优美\(O(n)\)。
代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
const int maxn = 100005, maxm = 100005;
const int base = 137;
const int mod = 19260817;
int n, m;
int a[maxn];
int b[maxm];
bool need[mod];
int vis[mod];
int ans1, ans2;
int hashe(char *ch)
{
long long ans = 0, len = strlen(ch);
for(int i = 0; i < len; i++)
{
ans = (ans * base + ch[i]) % mod;
}
return (int)ans;
}
int main()
{
scanf("%d", &n);
char temp[20];
for(int i = 1; i <= n; i++)
{
scanf("%s", temp);
//printf("%d\n", hashe(temp));
int has = hashe(temp);
need[has] = true;
a[i] = has;
}
scanf("%d", &m);
for(int i = 1; i <= m; i++)
{
scanf("%s", temp);
//printf("%d\n", hashe(temp));
int has = hashe(temp);
b[i] = has;
if(need[b[i]] && !vis[b[i]]) vis[b[i]] = true, ans1++;
}
if(ans1 == 0)
{
printf("0\n0\n");
return 0;
}
printf("%d\n", ans1);
ans2 = 1 << 30;
int l = 1, r = 1, cnt = ans1;
memset(vis, 0, sizeof(vis));
while(233)
{
if(!cnt)
{
while(!need[b[l]]) l++;
if(l == m + 1) break;
ans2 = std::min(ans2, r - l);
if(vis[b[l]] == 1) cnt++;
if(vis[b[l]] >= 1) vis[b[l]]--, l++;
}
else
{
if(r == m + 1) break;
if(need[b[r]])
{
if(!vis[b[r]]) cnt--;
vis[b[r]]++;
}
r++;
}
}
printf("%d\n", ans2);
return 0;
}