jzoj5462

题意求长度为n的字符串中的长度为m的连续子串有多少个是不同的。

比如n=5, s=aaaab

它长度为3的子串有

aaa、aaa、aab

有两个不同的子串,答案为2。

解法有两种,其一是hash,其二是后缀自动机。

这里讲讲hash。

我用的双hash。

大意就是第一个hash用来查询,第二个hash用来判第一个hash的冲突(冲突的概率极小)

hash的写法:

预处理:

h1[0] = h2[0] = 0;
    for (int i = 1; i <= len; i++)
    {
        h1[i] = (h1[i - 1] * 131 + s[i]) % m1;
        h2[i] = (h2[i - 1] * 97 + s[i]) % m2;
    } 

求出每个子串的hash值:

for (LL i = 1; i <= len - m + 1; i++)
    {
        LL k1 = ((h1[i + m -1] - h1[i - 1] * ksm(131, m, m1)) % m1 + m1) % m1;
        LL k2 = ((h2[i + m -1] - h2[i - 1] * ksm(97, m, m2)) % m2 + m2) % m2;
        hash(k1, k2);
    }

如上图所示

最后统计答案:

void add(LL k1, LL k2)
{
    a[tot].x = k2;
    a[tot].next = h[k1];
    h[k1] = tot++;
}

void hash(LL k1, LL k2)
{
    for (LL i = h[k1]; ~i; i = a[i].next)
        if (a[i].x == k2)
            return;
    add(k1, k2);
    ans++;
}

如果发现两个hash值都不同,则说明是一个新的子串。

完整代码:

#include <cstdio>
#include <cstring>
#define LL long long
using namespace std;

const LL maxn = 200005, m1 = 999973, m2 = 1000000000 + 9;

LL n, m, ans, tot;

LL h[m1 + 5], h1[maxn], h2[maxn];

char s[maxn];

struct node
{
    LL x, next;
}a[maxn];

void add(LL k1, LL k2)
{
    a[tot].x = k2;
    a[tot].next = h[k1];
    h[k1] = tot++;
}

void hash(LL k1, LL k2)
{
    for (LL i = h[k1]; ~i; i = a[i].next)
        if (a[i].x == k2)
            return;
    add(k1, k2);
    ans++;
}

LL ksm(LL a, LL b, LL mo)
{
    LL ans = 1, base = a;
    while (b)
    {
        if (b & 1)
            ans = (ans * base) % mo;
        base = (base * base) % mo;
        b >>= 1;
    }
    return ans % mo;
}

int main()
{
    freopen("article.in","r",stdin);
    freopen("article.out","w",stdout);
    ans = tot = 0;
    memset(h, -1, sizeof h);
    scanf("%lld%lld", &n, &m);
    scanf("%s", s + 1);
    LL len = strlen(s + 1);
    h1[0] = h2[0] = 0;
    for (int i = 1; i <= len; i++)
    {
        h1[i] = (h1[i - 1] * 131 + s[i]) % m1;
        h2[i] = (h2[i - 1] * 97 + s[i]) % m2;
    } 
    for (LL i = 1; i <= len - m + 1; i++)
    {
        LL k1 = ((h1[i + m -1] - h1[i - 1] * ksm(131, m, m1)) % m1 + m1) % m1;
        LL k2 = ((h2[i + m -1] - h2[i - 1] * ksm(97, m, m2)) % m2 + m2) % m2;
        hash(k1, k2);
    }
    printf("%lld\n", ans);
    return 0;
}
View Code

 P.S.:

这题我使用的是哈希表的方法,要求m1一定要比较小,m2不能取太大,否则后来的运算中可能会出现溢出的情况。

我一般取m1=999973,m2=1e9+9

这里还有另外一种方法。

我们把每次hash得到的两个数k1,k2,搞成一个pair。

然后把pair塞进一个vector中,排序并去重之,如此可以直接得到答案。

核心代码:

h1[0] = h2[0] = 0;
    for (int i = 1; i <= len; i++)
    {
        h1[i] = (h1[i - 1] * 131 + s[i]) % m1;
        h2[i] = (h2[i - 1] * 97 + s[i]) % m2;
    } 
    for (LL i = 1; i <= len - m + 1; i++)
    {
        LL k1 = ((h1[i + m -1] - h1[i - 1] * ksm(131, m, m1)) % m1 + m1) % m1;
        LL k2 = ((h2[i + m -1] - h2[i - 1] * ksm(97, m, m2)) % m2 + m2) % m2;
        v.push_back(make_pair(k1, k2));
    }
    sort(v.begin(), v.end());
    ans = unique(v.begin(), v.end()) - v.begin();
    printf("%lld\n", ans);

 PSS:

之前写的hash方法太慢,现在使用新的hash方法,大大提高了速度。(from O(nm) to O(n))

posted @ 2017-11-08 16:22  yohanlong  阅读(104)  评论(0编辑  收藏