OI 字符串 常用哈希方法

(同步个人博客 http://sxysxy.org/blogs/44 到csdn

下面这个方法来自 算法竞赛入门经典-训练指南

方法

对于一个字符串s,令h[i] = h[i+1]*x + s[i] ,其中x是你自选的一个常数。令xp[i] = xp[i-1]*x

这样之后定义s的起点为下标i,长度为len的子串的哈希值为 h[i] - h[i+len]*xp[len] 。这个值与子串的位置,子串的内容,还有你自选的常数都有关系。哈希值使用unsigned long long(如果不是oi,可以使用int128的话当然更好) 不同子串的哈希值一定不会相同吗?不一定,但是相同的概率非常非常小。如果觉得不够保险可以分别选定两次x常数,双哈希来做。

应用

dp超时,后缀数组RE了?(orz Fmuckss神犇),后缀自动机不会写?来写易于理解速度也相当不错的哈希吧。

这里有两个字符串,分别给两个字符串做哈希,然后二分直接二分答案len,计算两个字符串每个起点开始长度为len的子串的哈希(O(n)的)。然后给第两个字符串的长度为len的子串的哈希值排序。枚举第一个字符串的哈希,在第二个字符串的哈希里面用lower_bound找(这也是之前给它排序的原因,排好序就可以lower_bound),找到了就说明存在这样长度的最长公共子串。于是AC代码如下(最后总共用时约1800ms,约是后缀自动机的9倍):

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <list>
#include <queue>
#include <algorithm>
using namespace std;
typedef unsigned long long ULL;
#define MAXN 200003 
ULL xp[MAXN];
ULL hasha[MAXN];
ULL hashb[MAXN];
ULL ha[MAXN];
ULL hb[MAXN];

char A[MAXN], B[MAXN];
int x = 233;
int la, lb;
int mlen;
int minlen;
bool check(int len)
{
    for(int i = 0; i < la-len+1; i++)
        hasha[i] = ha[i] - ha[i+len]*xp[len];
    for(int i = 0; i < lb-len+1; i++)
        hashb[i] = hb[i] - hb[i+len]*xp[len];
    sort(hashb, hashb+lb-len+1);
    for(int i = 0; i < la-len+1; i++)
    {
        ULL h = hasha[i];
        int p = lower_bound(hashb, hashb+lb-len+1, h)-hashb;
        if(p < lb-len+1 && h == hashb[p])return true;
    }
    return false;
}

int main()
{
    scanf("%s %s", A, B);
    la = strlen(A);
    lb = strlen(B);
    mlen = max(la, lb);
    minlen = min(la, lb);
    for(int i = la-1; i >= 0; i--)
        ha[i] = ha[i+1]*x + A[i];
    for(int i = lb-1; i >= 0; i--)
        hb[i] = hb[i+1]*x + B[i];
    xp[0] = 1;
    for(int i = 1; i <= mlen; i++)
        xp[i] = xp[i-1]*x;
    int l = 0, r = minlen;
    int ans;
    while(l <= r)
    {
        int m = (l+r)>>1;
        if(check(m))
        {
            ans = m;
            l = m+1;
        }else
            r = m-1;
    }
    printf("%d\n", ans);
    return 0;
}

最长这个条件优先,因此还是二分长度。每次检查是否存在这样一个出现次数超过m次的子串即可。将哈希值排序可以把哈希值相同的子串的哈希值排在一起,这样之后就能O(n)时间判断出超过次数是否超过m次。代码

/*
submition url:
https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&category=505&page=show_problem&problem=3358
*/
#include <cstdio>
#include <cstdlib>
#include <cstdarg>
#include <cstring>
#include <list>
#include <queue>
#include <vector>
#include <algorithm>
#include <string>
using namespace std;
int m;
char str[40002];
typedef unsigned long long ULL;
ULL h[40002];
ULL hhash[40002];
ULL xp[40002];
int rrank[40002];
int x = 123;
int slen;
int pos;
bool cmp(int a, int b)
{
    return hhash[a] < hhash[b] || (hhash[a] == hhash[b] && a < b);
}
void calc()
{
    slen = strlen(str);
    h[slen] = 0;
    for(int i = slen-1; i >= 0; i--)
        h[i] = h[i+1]*x + str[i];
    xp[0] = 1;
    for(int i = 1; i <= slen; i++)
        xp[i] = xp[i-1]*x;
}
bool check(int len)
{
    for(int i = 0; i < slen-len+1; i++)
    {
        rrank[i] = i;
        hhash[i] = h[i] - h[i+len]*xp[len];
    }
    sort(rrank, rrank+slen-len+1, cmp);
    int times = 0;
    pos = -1;
    for(int i = 0; i < slen-len+1; i++)
    {
        if(i == 0 || hhash[rrank[i]] != hhash[rrank[i-1]])times = 0;
        if(++times >= m)
            pos = max(pos, rrank[i]);
    }
    return pos >= 0;
}
void solve()
{
    if(!check(1))
    {
        puts("none");
        return;
    }
    int l = 0, r = slen;
    int ans = 0;
    while(l <= r)
    {
        int m = (l+r)>>1;
        if(check(m))
        {
            ans = m;
            l = m+1;
        }else
            r = m-1;
    }
    check(ans);
    if(ans)
        printf("%d %d\n", ans, pos);
    else
        puts("none");
}
int main()
{
    while(true)
    {
        scanf("%d", &m);
        if(!m)break;
        scanf("%s", str);
        calc();
        solve();
    }
    return 0;
}

哈希用途广泛,是个非常好的东西。

  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值