[HDU 4080] Stammering Aliens (字符串哈希+二分)

链接

HDU 4080


题意

每组数据为一个整数m和一个长度不小于m的字符串,求该字符串的一个子串,该子串在满足出现次数不小于m的同时应尽量长。
输出该长度和最右侧出现的起始位置。如果存在多组数据,输出有最靠近右侧的那组。


思路

关于子串和长度的题目仿佛都可以用字符串哈希+二分来水一水。
这题仍然是字符串哈希的题目,对这个字符串求哈希,然后二分长度,枚举对应长度的子串哈希。每个哈希值要存储在哈希表里并记录次数,由于定长子串的哈希是从左向右线性枚举的,因此很容易找到最右侧的解。
我使用了双重哈希,存储哈希表的方式不太好,是沿着环顺序存储的而不是链式存储,这样发生冲突每次都要顺次去找位置,很耗时。自然溢出的单重哈希也是可以过的,只要存得好。


代码
#include <cstdio>
#include <iostream>
#include <cstring>
#include <set>
#include <map>
#include <vector>
using namespace std;
typedef unsigned long long ulint;
typedef pair<ulint, ulint> Hash;
#define maxn (40100)
const ulint seed = 30007uLL;
const ulint mod1 = 100007;
const ulint mod2 = 1e9 + 1017uLL;
ulint xp1[maxn], H1[maxn], xp2[maxn], H2[maxn];

int m, slen;
char s[maxn];

ulint h1[mod1], h2[mod1];
int t[mod1], vis[mod1];

pair<int, int> check(int len)
{
    memset(t, 0, sizeof(t));
    memset(vis, 0, sizeof(vis));
    int ans = -1;

    H1[0] = H2[0] = s[0] - 'a' + 1;
    for(int i = 1; i < len; i++)
    {
        H1[i] = (H1[i-1] * seed + s[i] - 'a' + 1) % mod1;
        //printf("H1[%d] = %llu\n", i, H1[i]);
        H2[i] = (H2[i-1] * seed + s[i] - 'a' + 1) % mod2;
        //printf("H2[%d] = %llu\n", i, H2[i]);
    }
    vis[H1[len-1]] = 1;
    h1[H1[len-1]] = H1[len-1];
    h2[H1[len-1]] = H2[len-1];
    t[H1[len-1]]++;
    if(t[H1[len-1]] >= m) ans = 0;

    for(int i = len; i < slen; i++)
    {
        H1[i] = ((H1[i-1] + mod1) - ((s[i-len] - 'a' + 1) * xp1[len-1]) % mod1) % mod1;
        H1[i] = (H1[i] * seed + s[i] - 'a' + 1) % mod1;

        H2[i] = ((H2[i-1] + mod2) - ((s[i-len] - 'a' + 1) * xp2[len-1]) % mod2) % mod2;
        H2[i] = (H2[i] * seed + s[i] - 'a' + 1) % mod2;

        int pos = H1[i];
        while(vis[pos] && (h1[pos] != H1[i] || h2[pos] != H2[i]))
        {
            pos = (pos + 1) % mod1;
        }
        vis[pos] = 1;
        h1[pos] = H1[i];
        h2[pos] = H2[i];
        t[pos]++;

        if(t[pos] >= m) ans = i + 1 - len;
    }

    return make_pair(len, ans);
}

int main()
{
    //freopen("4080.txt", "r", stdin);

    xp1[0] = xp2[0] = 1uLL;
    for(int i = 1; i < maxn; i++)
    {
        xp1[i] = (xp1[i-1] * seed) % mod1;
        xp2[i] = (xp2[i-1] * seed) % mod2;
    }

    while((cin >> m) && m)
    {
        scanf("%s", s);
        slen = strlen(s);

        int l = 1, r = slen, m;

        pair<int, int> ans = make_pair(0, -1);

        while(l <= r)
        {
            m = (l + r) >> 1;

            pair<int, int> ret = check(m);

            if(ret.second < 0) r = m - 1;
            else
            {
                ans = ret;
                l = m + 1;
            }
        }

        if(ans.second < 0) cout << "none" << endl;
        else cout << ans.first << " " << ans.second << endl;
    }

    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值