HDU4821 UVALive6711 String【哈希函数】

 

String

 

Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 3854    Accepted Submission(s): 1164

 

 

Problem Description

Given a string S and two integers L and M, we consider a substring of S as “recoverable” if and only if
  (i) It is of length M*L;
  (ii) It can be constructed by concatenating M “diversified” substrings of S, where each of these substrings has length L; two strings are considered as “diversified” if they don’t have the same character for every position.

Two substrings of S are considered as “different” if they are cut from different part of S. For example, string "aa" has 3 different substrings "aa", "a" and "a".

Your task is to calculate the number of different “recoverable” substrings of S.

 

 

Input

The input contains multiple test cases, proceeding to the End of File.

The first line of each test case has two space-separated integers M and L.

The second ine of each test case has a string S, which consists of only lowercase letters.

The length of S is not larger than 10^5, and 1 ≤ M * L ≤ the length of S.

 

 

Output

For each test case, output the answer in a single line.

 

 

Sample Input

 

3 3 abcabcbcaabc

 

 

Sample Output

 

2

 

 

Source

2013 Asia Regional Changchun

 

 

 

Regionals 2013 >> Asia - Changchun

 

问题链接HDU4821 UVALive6711 String

问题简述:(略)

问题分析

  字符串有关的算法,大致可以分为三类。一是像本题一样,用哈希函数来解(定长字符串);二是KMP算法(包括其变种);三是AC自动机。

  这个问题,由于子串之间需要相互比较的组合太多,为了避免重复的比较计算,需要找到一个有效的办法进行处理。不然就组合爆炸了。所以,字符串的哈希函数是一个好的选择。各个子串都计算一个哈希值,字符串比较问题就变成了哈希值比较的问题。进一步,把哈希值放入容器map中,就很快知道各个字串是否都不同(数一下数量)。m*l长的字符串,分为m个l长的子串,各个子串的哈希值作为key放入容器map中,如果容器map中有m个元素,说明各个字串都不相同。

  有关字符串哈希值的计算,可以参见:B00013 字符串哈希函数。其中的内容来自百度百科,可惜编码质量太差,不可以直接用的。

  计算哈希函数有各种各样的算法。本程序用的是BKDRHash算法,其中的基数一般取素数,以降低哈希值冲突的概率。这个基数,在实际计算时,可以看作是进制。

  计算各个字符串的哈希值的方法也是本程序的一个亮点。这里也是按照无名大神的做法做的。

  先计算函数hv(),对于字符串s,若其长度为n,则hv(n+1)=0,hv(i)=h(i+1)*base+第i个字符的ASCII值。这里的base为计算哈希值的基数。

  再计算函数nbase(),该函数定义为nbase(1)=1,nbase(i)=nbase(i-1)*base。

  这样,对于字符串s,第i个字符开始的长度为l的各个子串的哈希值hash(i)=hv(i)-hv(i+l)*nbase(l)。

  以上的哈希值计算方法,主要是为了减少计算量。

  同样是为了加快程序运行速度,程序中使用了一个带参数的宏定义“#define getHashval(n, l) hv[n] - hv[n+l] * nbase[l]”,比起使用函数来要好一些,至少省去了程序调用返回和参数传递。这也是有经验程序员的常见做法。

  其他需要说明的,都在程序注释里了。

程序说明:(略)

 

AC的C++语言程序如下:

 

/* HDU4821 UVALive6711 String */

#include <iostream>
#include <string>
#include <map>

using namespace std;

#define getHashval(n, l) hv[n] - hv[n+l] * nbase[l]

typedef unsigned long long ULL;

const int base = 131;
ULL hv[100000+1];
ULL nbase[100000+1];

map<ULL, int> hashmap; // 类型为map,实际当作一个集合来使用

int main()
{
    int m, l, count;
    string s;

    nbase[0] = 1;
    for(int i=1; i<=100000; i++)
        nbase[i] = nbase[i-1] * base;

    // 输入m和l
    while (scanf("%d%d", &m, &l) != EOF) {
        //输入字符串
        cin >> s;

        // 计数清零
        count = 0;

        // 计算长度为l,各个字串的哈希值
        int len = (int)s.length();
        hv[len] = 0;
        for(int i=len-1; i>=0; i--)
            hv[i] = hv[i+1] * base + s.at(i);

        // 窗口A,长度为m*l,在输入字符串上滑动,每次滑动1个字符,可以看到len-m*l+1个字串
        int end = len-m*l;
        for(int i=0; i<l && i<=end; i++) {
            //哈希值集合初始化:清空
            hashmap.clear();

            // 将长度为m*l的串,切割为长度为l的m个子串,分别将各个子串的哈希值放入hashmap集合
            // map中,map的key是子串的哈希值,map的值是一个计数,即相同哈希值出现n次的话则值为n
            for(int j=i; j<i+m*l; j+=l)
                hashmap[getHashval(j, l)]++;
            if((int)hashmap.size() == m)
                count++;

            // 窗口B,长度为m*l,开始与窗口A重合,每次向右滑动l个字符,到窗口右边与字符右边对齐为止
            // 每滑动一次B窗口,将最左边那长度l子串的哈希值移出hashmap集合,右边子串的哈希值添加到集合
            int end2 = len-m*l-l;
            for (int j=i; j<=end2; j+=l) {
                ULL temp = getHashval(j, l);
                hashmap[temp]--;
                if(hashmap[temp] == 0)
                    hashmap.erase(temp);

                hashmap[getHashval(j+m*l, l)]++;
                if((int)hashmap.size() == m)
                    count++;
            }
        }

        // 输出结果
        cout << count << endl;
    }

    return 0;
}

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值