( 标题并不是说哈希和对拍有什么关系,只是写这道题的时候刚好学明白了这两个东西罢了)
有关哈希算法,其实常用的也就是哈希字符串,而哈希对数字的处理就像是一个离散化的存在,能用哈希解决的离散化,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
以及非常感谢前辈们的指点教导!!!