【初识单哈希、对拍】最短公共超串

本文探讨了哈希算法在字符串处理中的应用,包括拉链法和开放寻址法,并介绍了单哈希在字符串哈希中的实现。同时,作者提到了对拍技术,即通过对比不同代码在特定数据上的输出差异来定位错误。重点讲述了如何在字符串最短公共超串问题中运用这些技术。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

( 标题并不是说哈希和对拍有什么关系,只是写这道题的时候刚好学明白了这两个东西罢了)

有关哈希算法,其实常用的也就是哈希字符串,而哈希对数字的处理就像是一个离散化的存在,能用哈希解决的离散化,map也可以。浅说一下拉链法和开放寻址法吧。

拉链法

const int maxn; //数据范围
int mod; //大于范围的一个质数
int h[maxn], e[maxn], ne[maxn], idx = 1; //结构体链式前向星存数据同理最短路里的内容

//初始化
memset(h, -1, sizeof h);

//向哈希表中插入一个数
void insert(int x)
{
    int k = (x % mod + mod) % mod;
    e[idx] = x;
    ne[idx] = h[k];
    h[k] = idx++;
}
//ps:它存储的内容就类似一个数链,每个取模后的位置上对应着许多个取模前的数据

//在哈希表中查询某个数是否存在
bool find(int x)
{
    int k = (x % mod + mod) % mod; //x可能会出现负数的情况,此时需要再加mod使其变成正数即可
    for (int i = h[k]; i != -1; i = ne[i]) {
        if (e[i] == x) {
            return true;
        }
    }
    return false;
}

开放寻址法

const int maxn; //数据范围
int mod; //大于范围的一个质数
int h[maxn];

//初始化
memset(h, -1, sizeof h);

//如果x在哈希表中,返回x的下标;如果x不在哈希表中,返回x应该插入的位置
int find(int x)
{
    int t = (x % mod + mod) % mod;
    while (h[t] != -1 && h[t] != x)
    {
        t++;
        if (t == maxn) t = 0;
    }
    return t;
}

 字符串单哈希(虽然我也不知道双哈希三哈希是什么但是这样说严谨一点)

#define ull unsigned long long

const int maxn;
const int base = 131; //这是某一个数学家的结论,我也不清楚我只会用,131或者13331都是很少会导致冲突的数

ull p[maxi], h1[maxi];

//初始化标记数组
void init()
{
	p[0] = 1; //字符串是从第一位开始的,所以输入完string之后再开头补一个字符即可a = ' ' + a;
	for (int i = 1;i <= maxn; i++) {
		p[i] = p[i - 1] * base % mod; //关于这个取模 下边细🔒
	}
}

//小知识:这里的函数名如果叫hash就报错了
//乐
void hs(string s, ull *h)
{
	int len = s.length();
	for(int i = 1; i <= len; i++) {
		h[i] = (h[i - 1] * base + s[i]) % mod; //相当于是把字符串转化成了数字存到了数组里
	}
}

ull gethash(int l,int r,ull *h)
{
	return (h[r] - h[l - 1] * p[r - l + 1] % mod + mod) % mod; //可视为得到某一段的字符串是什么,但是以数字的形式表达的
}

 代码中的取模其实是一个很玄乎的东西,用到单哈希解题的时候,其实是一个概率的问题,你要看这个出题人是否想要卡你数据,它可以卡掉任意的质数只要它想,而ull unsigned long long虽说是内置取模环节,但是下边遇到的那个题目他甚至卡掉了ull的取模,还必须手动取模1e9+7才可以成功,甚至这个1e9+7的质数也是可以被卡掉的。

假设我们是在cf遇见这类问题的时候,如果有人有恶意非得hank你,用单哈希的你真的就无能为力了。

话说回来,把字符串这样储存读取确实是一个很方便的东西捏~

对拍

关于对拍,其实就是拿两个代码,一个已ac的(也可以是赛场上的一个暴力求的铁正解),一个fv我写的,放到文件里,外加一个造随机数据的代码(一定要是那种可以造得出来数据的题目才能使用对拍来着嘞)

三者通过一些奇妙的反应,找到ac和fv我的代码的输出差异,从而找到错误样例,就好比这道题。

对拍板子

#include <cstdio>
#include <cstdlib>
#include <ctime>
using namespace std;
int main()
{
    while (1) //一直循环,直到找到不一样的数据
    {
        system("Random.exe > in.txt");
        system("Bf.exe < in.txt > baoli.txt"); //两个代码的文件名分别叫Bf和Sol,如果找到差异样例,则输入存储在in.txt里,两种输出在另外两个txt里
        system("Sol.exe < in.txt > std.txt");
        if (system("fc std.txt baoli.txt")) //当 fc 返回1时,说明这时数据不一样
            break;                          //不一样就跳出循环
        else
			printf("NOW yes\n"); 
    }
    return 0;
}

造数据代码

当时的我是没有发现一个字符串包含另一个字符串的特判情况,用以下代码就能找到了

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


int main(){
//    freopen("inTemp.txt","r",stdin);
//    freopen("data.in", "w", stdout);
    srand(int(time(0)));
    string a, b;
    a.resize(6);
    b.resize(2);
    for (int i = 0; i <= 5; i++) {
		char x = 'a' + rand() % 5; //取了 a 到 e
		a[i] = x;
	}
	for (int i = 0; i <= 1; i++) {
		char x = 'a' + rand() % 5;
		b[i] = x;
	}
//	int a = rand() % 100 + 1; //这个翁恺讲过怎么取一个范围内的数据,这行取得就是1到100
//	int b = rand() % 100 + 1;
	cout << a << " " << b << endl;
}

 遇到的题目

题目链接:

F-最短公共超串_“帆软杯”武汉大学2022级新生程序设计竞赛 (nowcoder.com)

题目:

时间限制:C/C++ 1秒,其他语言2秒
空间限制:C/C++ 524288K,其他语言1048576K
Special Judge, 64bit IO Format: %lld

题目描述

给定两个字符串 str1 和 str2 ,请返回同时以 str1 和 str2 作为子串的最短字符串。

称字符串 A 为另一个字符串 B 的子串,当且仅当将 B 的首部和尾部分别删除若干个字符(数量均可以为零)后得到 A 。例如,abc,bcd,cd,abcde等都是 abcde的子串,而 ac,bde不是。

输入描述:

输入共两行,每行一个只包含小写字母的字符串,分别表示 str1,str2(∣str1∣,∣str2∣≤2×105) 。(绝对值符号 |s|表示字符串的长度)

输出描述:

输出一行一个字符串,表示同时以 str1 和 str2​ 作为子串的最短字符串。

如果存在多个答案,可以输出任意一个。

示例1

输入

abac
cab

输出

cabac

说明

对于样例1,可以证明没有比 cabac 更短的同时包含两个给定字符串作为子串的字符串了:cabac 删掉最前面一个字符得到 abac,删掉最后面两个字符得到 cab。

示例2

输入

bbbaaaba
bbababbb

输出

bbababbbaaaba

 思路:(可能依然很蠢

将两字符a,b串储存到哈希数组后,比较a的末位和b的前位,比较a的前位和b的末位,特判a长度比b小2的时候如果b包含了a,b长度比a小2的时候如果a包含了b,a b完全不等,a b完全相等(怎么又被我写成了大模拟0^0)

已AC代码:

#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<iostream>
#include<math.h>
#include<queue>
#include<map>
#include<vector>
#define ll long long
#define pii pair<int,int>
using namespace std;
#define ull unsigned long long
const int maxn = 1e6 + 10;
ull ha[maxn], hb[maxn], p[maxn];
int base = 131, mod = 1e9 + 7;
void init()
{
	p[0] = 1;
	for (int i = 1; i <= 2e5 + 5; i++) {
		p[i] = p[i - 1] * base % mod;
	}
}
void hs(string s, ull *h) 
{
	int len = s.length();
	for (int i = 1; i <= len; i++) {
		h[i] = (h[i - 1] * base + s[i]) % mod;
	}
}
int gethash(int l, int r, ull *h)
{
	return (h[r] - h[l - 1] * p[r - l + 1] % mod + mod) % mod;
}
signed main()
{
	init();
	string a, b, aa, bb;
	cin>>a>>b;
	aa = a, bb = b;
	if (a == b) {
		cout<<a;
		return 0;
	}
	int len1 = a.length(), len2 = b.length();
	a = ' ' + a;
	b = ' ' + b;
	hs(a, ha);
	hs(b, hb);
	int ans = 0, flag = 0, cnt;
	for (cnt = 0; cnt <= len2; cnt++) {
		if (gethash(len2 - cnt, len2, hb) == gethash(1, 1 + cnt, ha)) { //第一种
			if (cnt >= ans) {
				flag = 1;
				ans = cnt;
			}
		}
	}
	for (cnt = 0; cnt <= len1; cnt++) {
		if (gethash(len1 - cnt, len1, ha) == gethash(1, 1 + cnt, hb)) { //第二种
			if (cnt >= ans) {
				flag = 2;
				ans = cnt;
			}
		}
	}
	if (len1 <= len2 - 2) {
		for (int i = 2; i <= len2 - len1; i++) {
			if (gethash(i, i + len1 - 1, hb) == gethash(1, len1, ha)) { //第三种
				flag = 3;
				break;
			}
		}
	}
	if (len2 <= len1 - 2) {
		for (int i = 2; i <= len1 - len2; i++) {
			if (gethash(i, i + len2 - 1, ha) == gethash(1, len2, hb)) { //第四种
				flag = 4;
				break;
			}
		}
	}
	string anss;
	if (flag == 2) {
		for (int i = 1; i < len1 - ans; i++) {
			anss += a[i];
		}
		anss += bb;
	} else if (flag == 1) {
		for (int i = 1; i < len2 - ans; i++) {
			anss += b[i];
		}
		anss += aa;
	} else if (flag == 0) {
		anss += aa + bb;
	} else if (flag == 3) {
		anss = bb;
	} else if (flag == 4) {
		anss = aa;
	}
	cout<<anss;
	return 0;
}

by yq

以及非常感谢前辈们的指点教导!!!

评论 15
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

超高校级のDreamer

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值