poj1200 Crazy Search

@(K ACMer)


题意:

给你一个字符串,其中由nc个不同的字符组成.问你它的长度为n的不同的子序列有多少个?


分析:

  • 咋一看最直接的想法就是每一个子串都加入set中,然后最后看set中有多少个字符串就可以了.复杂度是 O(nstrlenlogn) 显然超时.
  • 那么有什么优化呢?
  • 我们可以看出每次往set中添一个字符串,这个字符串会和已经有的字符串比较是否相等,比较的时候就是逐个比较ASCII码.显然这个是非常高时间复杂度的,有没有不要每次的比较那么多次呢?
  • 这是字符串hash就出场了,把一个字符串转换成一个对应的数字,这样就可以快速比较这两个字符串相同与否了.但是要怎样确保两个不同的字符串产生的hash值就一定不相同呢?
  • 这时候就需要用到进制数的思想了,我们可以知道一个n进制数,它其中的字符有n个,n个字符组成的数,一定是特别的.且它的每一位是有不同的位权的.这样对于一个有nc个不同字符的字符串,我们可以对其中的每一个不同的字符给它一个从0开始的编码,最后把它表示成一个nc进制的数,算出这个数的值,就可以确保每一个字符串对应的hash值唯一.有一种hash算法就是转换成131进制数,其实也是这个道理,不过这个题必须压缩为nc进制,因为进制越大最后的数值也就越大很容易溢出,溢出之后的值很大程度上是由字符串前面几位的值确定的,所以就很容易碰撞.
  • 这里还要继续优化,这里我们没算出一个子串的hash值不是把它加入set里,而是打了一个hash表来容纳这些字符串.实现了 O(n) 的复杂度解决问题.
  • 最后我再计算每一个子串的时候利用了一点DP的思想.虽然并没有使94ms的时间更快=_=

code:

#include <iostream>
#include <cstdio>
#include <set>
#include <cstring>
using namespace std;
const int M = 17000000;
int n, nc, mod = int(1e9) +1123;
unsigned long long hx, pow[M];
char s[M];
bool hasht[M];
int key[300];


int main(void) {
    while (~scanf("%d%d%s", &n, &nc, s)) {
        int len = strlen(s);
        memset(hasht, false, sizeof(hasht));
        memset(key, 0, sizeof(key));
        int id = 0;
        for (int i = 0; i < len; i++) {
            if (!key[s[i]]) {
                key[s[i]] = id++;
            }
        }
        pow[0] = 1;
        for (int i = 1; i < n; i++) {
            pow[i] = pow[i - 1] * nc;
        }
        int ans = 1;
        hx = 0;
        for (int j = 0; j < n; j++) {
            hx += key[s[j]] * pow[j];
        }
        hasht[hx % (M - 13)] = true;
        for (int i = n; i < len; i++) {
            hx -= key[s[i - n]];
            hx /= nc;
            hx += key[s[i]] * pow[n - 1];
            if (!hasht[hx % (M - 13)]) ans++;
            hasht[hx % (M - 13)] = true;
        }
        printf("%d\n", ans);
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值