字符哈希算法

题目描述:给定一个长度为 n 仅由小写字母构成的字符串 S,再给定 q组查询,每次查询给定 4 个整数 l1,r1,l2,r2 你需要输出 Sl1​∼r1​​ 与 Sl2∼r2是否相同。

题目链接:http://47.121.118.174/p/80

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
// N: 字符串最大长度, B: 进制数(通常选131或13331), mod: 大质数(用于防溢出和冲突)
const int N=2e5+10, B=131, mod=998244353; 

ll pre[N];  // 存放字符串的前缀哈希值
ll base[N]; // 存放 B 的 i 次方,方便后续 O(1) 提取区间哈希

string s;
int n, q;

// 预处理函数:计算 base 数组和前缀哈希数组 pre
void init(){
    base[0]=1; // B 的 0 次方是 1
    for(int i=1; i<=n; i++){
        // 递推计算 B 的 i 次方,记得取模
        base[i] = (base[i-1] * B) % mod;
        
        // 计算前缀哈希:前一个状态的值乘上进制 B,加上当前字符的 ASCII 相对值
        // 这里减去 '0' 是为了把字符映射成数字,只要映射规则唯一即可
        pre[i] = (pre[i-1] * B + s[i] - '0') % mod; 
    }
} 

// 核心功能:O(1) 获取区间 [l, r] 的哈希值
ll getH(int l, int r){
    // 将 pre[l-1] 乘以 B 的 (r-l+1) 次方,使其与 pre[r] 的位数对齐后相减
    // 注意:因为 C++ 中负数取模还是负数,所以要加上 mod 再取模,保证结果为正
    return ((pre[r] - pre[l-1] * base[r-l+1]) % mod + mod) % mod;
}

int main(){
    // 优化输入输出速度
    ios::sync_with_stdio(false);
    cin.tie(0);
    
    cin >> n >> q;
    cin >> s;
    
    // 关键操作:在原字符串前面垫一个空格,使其有效字符下标从 1 开始
    s = " " + s; 
    
    init(); // 必须在字符串读入并处理好下标后,再进行预处理
    
    for(int i=1; i<=q; i++){
        int l1, r1, l2, r2;
        // 每次必须读满 4 个变量
        cin >> l1 >> r1 >> l2 >> r2; 
        
        // 比较两段区间的哈希值,如果相等则认为子串相同
        if(getH(l1, r1) == getH(l2, r2)) cout << "Yes" << "\n";
        else cout << "No" << "\n";
    }
    return 0;
}

注意点:负数取模问题(最容易全盘崩溃的坑) 在 getH 函数中,计算区间哈希的公式是基于前缀和相减的:

H(l,r)=(pre[r]−pre[l−1]×Br−l+1)(modM)在 C++ 中,如果 pre[r] 小于 pre[l-1] * base[r-l+1] 取模后的值,相减就会得到一个负数。C++ 对负数取模(比如 -5 % 3)得到的是 -2 而不是正数 1。

正确写法:必须像你代码里那样写成 ((A - B) % mod + mod) % mod,这能强制把负数“拉回”到正数范围内。

  1. 数据类型溢出(忘记开 long long) 在计算 base[i] 和 pre[i] 时,中间过程会发生乘法:pre[i-1] * B。 如果 pre 数组用的是普通的 32 位 int,这两个数相乘极容易超过 2×10 9 (int 的上限),导致直接溢出变成负数,彻底污染后续所有的哈希值。

防坑指南:哈希值的数组和相关计算函数,必须使用 long long(也就是你的 ll),你这点做得很好。

  1. 忘记对齐下标(数组越界) 就像我们之前聊过的,哈希区间查询需要用到 pre[l-1]。如果字符串从 0 开始存,当查询 l = 0 时,就会访问 pre[-1] 导致段错误或读到脏数据。

防坑指南:永远记得在输入字符串后加上 s = " " + s;,强制让有效下标从 1 开始。

  1. 哈希冲突(被出题人“卡”数据) 你使用的这套被称为单哈希(Single Hash)。你选了 B=131 和 mod=998244353。通常情况下这足以应付大部分题目。 但是在稍微高级的比赛中(比如 Codeforces),有些坏心眼的出题人会专门构造针对常见 mod(如 10 9 +7 或 998244353)的“碰撞数据”,导致两个不同的字符串算出完全一样的哈希值,从而输出错误的 Yes。

进阶防坑:如果你发现逻辑全对但就是过不了某些测试点(WA),说明你被卡哈希了。解决方法是换一个冷门的质数(比如 mod=109+9 或更大),或者直接写双哈希(用两组不同的 B 和 mod 分别计算,两次都相等才算相等)。自然溢出法(直接用 unsigned long long 让它自动溢出代替取模)现在非常容易被针对,不建议使用。

全部评论

相关推荐

就是问做测试开发或者说做测试有没有前景?这个东西我可以分多个方面去跟你说,首先就是宏观的大环境大背景下,在这个AI时代充满变革的时代,不管你做什么岗位都不一定很有未来,还有前景,除非你是做最最最热门的大模型的预训练,大模型的后训,练大模型的推理相关的岗位,这种的岗位一般一个月会给你10个w,一年给你100个w,这种的肯定是有前景的,即使你干几年,你这辈子也有了,然后就是做测试开发的这个序列整体的人才梯队的这个画像就不是很优秀,你放眼到5年之前,放眼到10年之前有大专的来做,5年之前呢就是普通的本科非科班的同学来做到,现在呢也是有很多想走捷径的,985的那种混子同学想来做这种,所以整体他这个序列的人才就不是很优秀的,就不是最优秀的那一梯队那一档。由此可见前景就堪忧,还有就是这个工作的性质就决定了,你是做一个质量保障的岗位,你是做一个守门员的岗位,如果说没有问题,正常交付,如期上线,这就是你应有的职责,别人不会夸奖你的,但如果出现了漏测,出现了事故,出现了告警,那么第一个就拿你开刀,可以说是好事儿,你沾不着,坏事儿就让你来背,!但还有就是工作的场景,工作的逻辑十分的复杂,工作的业务非常的烧脑,还是需要费心耗神的一份工作。。再就是确实是工作内容也决定了他就&nbsp;&nbsp;&nbsp;是做一些一成不变的,无聊的,枯燥的,乏味的一些点点点的工作,呃,AI提效相关的一些自动化的建设的话,如果说业务前景很好,那么你是没空做这些相关的建设的,除非你周末自愿加班,或者说天天干到凌晨1点2点来做这些东西。因此总体而言,作为一个陌生人,作为一个敢于说真话的,一个直接的人,会跟你说没有前景的,但是如果是一个亲近的人,一个跟你关系比较好的人,你的父母,你的朋友,他肯定还是会说你找了一份不错的工作的。因为说好话别人爱听,自己也不吃亏,但别人吃不吃亏就无关了
asdasdasda...:我觉得就是什么样的能力干什么样的事,很多人就是心比天高,命比纸薄,对没那么好的工作看不上,好的自己又不太够得着,如果有足够的能力达到自己想要的,又怎么会问这种问题呢,直接走最好的。 有没有前景这些问题,但凡会上网自己都能找信息与数据,说不好听的就是觉得自己走投无路想走测试,然后想找个测试相关行业的人员听几句好话自我安慰罢了
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务