zoukankan      html  css  js  c++  java
  • 单词检索(search)

    单词检索(search)

    \(Description\)

    小可可是学校图书馆的管理员,现在他接手了一个十分棘手的任务。
    由于学校需要一些材料,校长需要在文章中检索一些信息。校长一共给了小可可N篇文章,每篇文章为一个字符串。现在,校长需要他找到这样的单词,它至少在这N篇文章中的M篇文章里出现过,且单词长度为L。可是,工作量十分庞大,但校长又急需小可可完成这项任务。
    现在他向你求助,需要你编写程序完成这项艰巨的任务。

    \(Input\)

    第1行3个正整数N,M,L,表示文章的数目,单词至少出现在M篇文章中和每个单词的长度。
    接下来N行,每行一个字符串,表示一篇文章。

    \(Output\)

    仅一行,表示满足检索条件的单词数。

    \(Sample Input\)

    3 2 2
    noip
    istudycpp
    imacppstudent

    \(Sample Output\)

    5
    【样例解释】
    这5个单词分别为:st,tu,ud,pp,cp。

    \(Data Constraint\)

    对于20%的数据有1≤N,M≤10;
    对于60%的数据有1≤N,M≤100;
    对于100%的数据有1≤N,M≤2000,L≤1000。每篇文章长度不大于1000,均有小写字母组成。

    思路

    字符串?
    既然限定了答案字符的长度,那很容易想到分离字符串,统计出现个数
    那就是 \(O(nl)\) 级别的个数,空间可以承受
    然后怎么存分离出的字符
    如果可以
    对于每一篇文章,我们把分离的字符去重(因为答案要求的字符至少在N篇文章中的M篇文章里出现过)
    最后合在一起
    此时统计出现次数超过 \(M\) 的字符数量就是答案
    想想,没有问题
    关键是分离
    如果把每个字符截取下来,那么一片文章处理的复杂度就是 \(O(|S|l)\)
    \(n\) 篇就是 \(O(n|S|l)\) 的,显然不可以承受
    然后呢?

    正解

    既然有了思路,那就直接打吧(也有 \(60\) 分了)
    可是,现在是追求正解的时候
    思路中的瓶颈在于分离字符
    而我们知道要获取一段区间的信息,如果满足可加性,那就可以前缀和维护
    字符串?
    哦,哈希!

    具体方式

    对于一个字符串,我们对其进行哈希时,考虑映射成一个数字
    一个很巧妙的方法是:把字符本身当做一串数字,一个字符对应权值
    从高到低每一位的位权就参考十进制的方法,记作 \(B^{k-1}\)
    其中 \(k\) 为第 \(k\) 位,\(B\) 是几进制
    因为题中字符串均由小写字母构成
    所以设为 \(26\) 进制(不这样也行,哈希映射的方法自己选择)
    那么对于一个字符串S,有:

    \[H(S) = \sum_{i=1}^{|S|} c_iB^{|S|-i} \]

    截取 S 中后 \(l\) 个字符组成的字符串设为 C',前 \(|S| - l\) 个字符组成的字符串设为 C

    \[H(C') = H(S) - H(C) * B^l \]

    于是维护 \(s_i =\sum_{k=1}^i H(|1...k|)\)

    双模数,以防万一

    代码

    #include<cstdio>
    #include<algorithm>
    using namespace std;
    typedef long long LL;
    const LL p1 = 1e9 + 7 , p2 = 1e9 + 9 , B = 26;
    LL s1[1005] , s2[1005] , b1[1005] , b2[1005];
    struct node{
    	LL x , y;
    }d[2000005] , c[2000005]; 
    int cnt , tot , n , m , l , ans;
    char ch;
    
    inline bool cmp(node a , node b){return (a.x < b.x || (a.x == b.x && a.y < b.y));}
    
    int main()
    {
    	scanf("%d%d%d" , &n , &m , &l);
    	b1[0] = b2[0] = 1;
    	for(register int i = 1; i <= 1001; i++) b1[i] = b1[i - 1] * B % p1;
    	for(register int i = 1; i <= 1001; i++) b2[i] = b2[i - 1] * B % p2;
    	int x;
    	for(register int i = 1; i <= n; i++)
    	{
    		x = 0 , s1[0] = 0 , s2[0] = 0 , cnt = 0;
    		ch = getchar();
    		while (ch < 'a' || ch > 'z') ch = getchar();
    		for(; ch >= 'a' && ch <= 'z'; ch = getchar())
    		{
    			x++;
    			s1[x] = (s1[x - 1] * B % p1 + (ch - 'a' + 1)) % p1;
    			s2[x] = (s2[x - 1] * B % p2 + (ch - 'a' + 1)) % p2;
    			if (x >= l)
    			{
    				d[++cnt].x = (s1[x] + p1 - s1[x - l] * b1[l] % p1) % p1;
    				d[cnt].y = (s2[x] + p2 - s2[x - l] * b2[l] % p2) % p2;
    			}
    		}
    		sort(d + 1 , d + cnt + 1 , cmp);
    		c[++tot] = d[1];
    		for(register int j = 2; j <= cnt; j++)
    		if (d[j].x != d[j - 1].x || d[j].y != d[j - 1].y) 
    			c[++tot] = d[j];
    	}
    	sort(c + 1 , c + tot + 1 , cmp);
    	x = 1;
    	while (x <= tot)
    	{
    		int s = 1;
    		while (c[x].x == c[x + 1].x && c[x].y == c[x + 1].y && x < tot) x++ , s++;
    		if (s >= m)	ans++;
    		x++;
    	}
    	printf("%d" , ans);
    }
    
  • 相关阅读:
    Java系列: JAVA字符串格式化-String.format()的使用(zz)
    Eclipse系列: 在Eclipse中用TODO标签管理任务(Task)(ZZ)
    JNDI全面总结(zz)
    Java Platform Standard Edition 8 Documentation
    JAVA NIO是什么(zz)
    Java NIO框架Mina、Netty、Grizzly介绍与对比(zz)
    Bootstrap系列 -- 25. 下拉菜单分割线
    Bootstrap系列 -- 24. 下拉菜单基本用法
    Bootstrap系列 -- 23. 图片
    Bootstrap系列 -- 22. 按钮详解
  • 原文地址:https://www.cnblogs.com/leiyuanze/p/12368625.html
Copyright © 2011-2022 走看看