2.9今日总结

昨天看了一些关于hash的博客,但是什么也不太懂。

今天算是有点收获。

上午3个半小时学会了hash,它的意思就和数学中函数一样,一个数对应一个数,并且不相同,

hash的模板:

int sum=0;

sum=(sum*base+a[i])%mod;

base 表示进制,即将一个数表示为base进制的数,但是肯定会超出范围,所以可以进行取模操作来轻松保存,但是可能会有重复的hash值,因此取模的数应该非常大,这样可以减小误差,然后base最好取131,1331,13331,133331......(虽然不知道为什么,但是其他博主都这样说)。

做完这些操作后可能还会有相同的,

所以有几种改良方法:

1.两个数hash值相同的话,可以比较原数是否相同或者比较地址。

2.双hash

即用两次hash,让两次hash取模的数不一样即可,然后再进行比较,只有两个hash都相同的时候才能证明两个值相同。

P3370 【模板】字符串哈希

题目描述

如题,给定 NN 个字符串(第 ii 个字符串长度为 M_iMi​,字符串内包含数字、大小写字母,大小写敏感),请求出 NN 个字符串中共有多少个不同的字符串。

友情提醒:如果真的想好好练习哈希的话,请自觉,否则请右转PJ试炼场:)

输入格式

第一行包含一个整数 NN,为字符串的个数。

接下来 NN 行每行包含一个字符串,为所提供的字符串。

输出格式

输出包含一行,包含一个整数,为不同的字符串个数。

输入输出样例

输入 #1复制

5
abc
aaaa
abc
abcc
12345

输出 #1复制

4

说明/提示

对于 30\%30% 的数据:N\leq 10N≤10,M_i≈6Mi​≈6,Mmax\leq 15Mmax≤15。

对于 70\%70% 的数据:N\leq 1000N≤1000,M_i≈100Mi​≈100,Mmax\leq 150Mmax≤150。

对于 100\%100% 的数据:N\leq 10000N≤10000,M_i≈1000Mi​≈1000,Mmax\leq 1500Mmax≤1500。

样例说明:

样例中第一个字符串(abc)和第三个字符串(abc)是一样的,所以所提供字符串的集合为{aaaa,abc,abcc,12345},故共计4个不同的字符串。

Tip: 感兴趣的话,你们可以先看一看以下三题:

BZOJ3097:http://www.lydsy.com/JudgeOnline/problem.php?id=3097

BZOJ3098:http://www.lydsy.com/JudgeOnline/problem.php?id=3098

BZOJ3099:http://www.lydsy.com/JudgeOnline/problem.php?id=3099

如果你仔细研究过了(或者至少仔细看过AC人数的话),我想你一定会明白字符串哈希的正确姿势的^_^

解题思路:将每个字符串表示为hash值,然后进行匹配(最好先进行排序,这样相同的会在一块)。

bug:这里之前我用单hash,但是有些问题,可能mod的数太小了。

代码实现:

#include<bits/stdc++.h>
using namespace std;
typedef unsigned long long ull;//自定义
struct node{
	int x;//两种hash值
	int y;
};
ull base=1331;//进制
ull mod=154311411511;//取模
ull mod1=73114111513;//取模
node a[100000];
char b[100000];
ull hash1(char c[])
{
	int l=strlen(c);
	ull sum=0;
	for(int i=0;i<l;i++)
	{
		sum=(sum*base+c[i])%mod;//转化进制
	}
	return sum;
}
ull hash2(char c[])
{
	int l=strlen(c);
	ull sum=0;
	for(int i=0;i<l;i++)
	{
		sum=(sum*base+c[i])%mod1;
	}
	return sum;
}

bool compare(node g,node h)//按x的大小进行比较
{
	return g.x<h.x;
}
int main()
{
	int n;
	cin>>n;
	for(int i=0;i<n;i++)
	{
		cin>>b;
		a[i].x=hash1(b);
		a[i].y=hash2(b);
	}
	sort(a,a+n,compare);
	int cnt=1;
	for(int i=1;i<n;i++)
	{
		if(a[i].x!=a[i-1].x||a[i].y!=a[i-1].y)
		cnt++;
	}
	cout<<cnt;
}

 

下午3个半小时,学长讲了kmp,感觉有点乱,后面自己作图模拟了一下,感觉应该领悟了一点。

核心代码:

void make()
{
	int j=0;
	int k=-1;
	next1[0]=-1;//将第一个位置赋值为-1,并且next所对应的数是前一个字符串的。 
	while(j<m)
	{
		if(k==-1||b[j]==b[k])//进行匹配 
		{
			j++;
			k++;
			next1[j]=k;
		}
		else
		k=next1[k];//返回 
	}
}

这个next数组用于寻找前缀后缀相同的最大值

a b c a b a b

第一个next[0]=-1,next的下标都是往后移了一个,next[2]表示字符串“ab”中相同的,

next[3]表示‘abc’中的。如果后移的一个数与之前前缀相同点后移一个数相同的话,此时这个点的next值加一,然后继续移。如果不相同,则跳到之前的k所在的next值。

P3375 【模板】KMP字符串匹配

 

题目描述

给出两个字符串 s_1s1​ 和 s_2s2​,若 s_1s1​ 的区间 [l, r][l,r] 子串与 s_2s2​ 完全相同,则称 s_2s2​ 在 s_1s1​ 中出现了,其出现位置为 ll。
现在请你求出 s_2s2​ 在 s_1s1​ 中所有出现的位置。

定义一个字符串 ss 的 border 为 ss 的一个非 ss 本身的子串 tt,满足 tt 既是 ss 的前缀,又是 ss 的后缀。
对于 s_2s2​,你还需要求出对于其每个前缀 s's′ 的最长 border t't′ 的长度。

输入格式

第一行为一个字符串,即为 s_1s1​。
第二行为一个字符串,即为 s_2s2​。

输出格式

首先输出若干行,每行一个整数,按从小到大的顺序输出 s_2s2​ 在 s_1s1​ 中出现的位置。
最后一行输出 |s_2|∣s2​∣ 个整数,第 ii 个整数表示 s_2s2​ 的长度为 ii 的前缀的最长 border 长度。

输入输出样例

输入 #1复制

ABABABC
ABA

输出 #1复制

1
3
0 0 1 

说明/提示

样例 1 解释

对于 s_2s2​ 长度为 33 的前缀 ABA,字符串 A 既是其后缀也是其前缀,且是最长的,因此最长 border 长度为 11。

数据规模与约定

本题采用多测试点捆绑测试,共有 3 个子任务

  • Subtask 1(30 points):|s_1| \leq 15∣s1​∣≤15,|s_2| \leq 5∣s2​∣≤5。
  • Subtask 2(40 points):|s_1| \leq 10^4∣s1​∣≤104,|s_2| \leq 10^2∣s2​∣≤102。
  • Subtask 3(30 points):无特殊约定。

对于全部的测试点,保证 1 \leq |s_1|,|s_2| \leq 10^61≤∣s1​∣,∣s2​∣≤106,s_1, s_2s1​,s2​ 中均只含大写英文字母。

bug:之前我一直用strlen(a)表示长度,而没用参数表示导致时间超限。

 代码实现:

#include<bits/stdc++.h>
using namespace std;
char a[1000001];
char b[1000001];
int next1[1000001];//前缀 
int n,m; 
void make()
{
	int j=0;
	int k=-1;
	next1[0]=-1;//将第一个位置赋值为-1,并且next所对应的数是前一个字符串的。 
	while(j<m)
	{
		if(k==-1||b[j]==b[k])//进行匹配 
		{
			j++;
			k++;
			next1[j]=k;
		}
		else
		k=next1[k];//返回 
	}
}

void kmp()
{   
    int x=0;//a数组现在遍历的位置 
	int y=0; //b数组现在遍历的位置 
	while(x<n)
	{
		if(y==-1||a[x]==b[y])
		{
			y++;
			x++;
		}
		else
		 y=next1[y];//回溯 
		 if(y==m)
		 {
		 	printf("%d\n",x-strlen(b)+1);
		 	y=next1[y];
		  } 
	 } 
}

int main()
{
      scanf("%s",a);
      scanf("%s",b);
      n=strlen(a);
      m=strlen(b);
	make();
    kmp();
    for(int i=1;i<=m;i++)
    {
     printf("%d ",next1[i]);
}
}

 

晚上1个半小时

P2957 [USACO09OCT]Barn Echoes G

题目描述

The cows enjoy mooing at the barn because their moos echo back, although sometimes not completely. Bessie, ever the excellent

secretary, has been recording the exact wording of the moo as it goes out and returns. She is curious as to just how much overlap there is.

Given two lines of input (letters from the set a..z, total length in the range 1..80), each of which has the wording of a moo on it, determine the greatest number of characters of overlap between one string and the other. A string is an overlap between two other strings if it is a prefix of one string and a suffix of the other string.

By way of example, consider two moos:

moyooyoxyzooo

yzoooqyasdfljkamo

The last part of the first string overlaps 'yzooo' with the first part of the second string. The last part of the second string

overlaps 'mo' with the first part of the first string. The largest overlap is 'yzooo' whose length is 5.

POINTS: 50

奶牛们非常享受在牛栏中哞叫,因为她们可以听到她们哞声的回音。虽然有时候并不能完全听到完整的回音。Bessie曾经是一个出色的秘书,所以她精确地纪录了所有的哞叫声及其回声。她很好奇到底两个声音的重复部份有多长。

输入两个字符串(长度为1到80个字母),表示两个哞叫声。你要确定最长的重复部份的长度。两个字符串的重复部份指的是同时是一个字符串的前缀和另一个字符串的后缀的字符串。

我们通过一个例子来理解题目。考虑下面的两个哞声:

moyooyoxyzooo

yzoooqyasdfljkamo

第一个串的最后的部份"yzooo"跟第二个串的第一部份重复。第二个串的最后的部份"mo"跟第一个串的第一部份重复。所以"yzooo"跟"mo"都是这2个串的重复部份。其中,"yzooo"比较长,所以最长的重复部份的长度就是5。

输入格式

* Lines 1..2: Each line has the text of a moo or its echo

输出格式

* Line 1: A single line with a single integer that is the length of the longest overlap between the front of one string and end of the other.

输入输出样例

输入 #1复制

abcxxxxabcxabcd 
abcdxabcxxxxabcx 

输出 #1复制

11 

说明/提示

'abcxxxxabcx' is a prefix of the first string and a suffix of the second string.

思路:运用hash,然后求出每个字符串子串的前缀和,然后比较前缀和

代码实现:

#include<bits/stdc++.h>
using namespace std;
char a[1000000];
char b[1000000];
typedef unsigned long long ull;
ull base=1331;
ull mod=1215411311541;
ull firsta[1000000];
ull backa[1000000];
ull firstb[1000000];
ull backb[1000000]; 
int first_hashs(char c[],int x)//hash 
{
	ull sum=0;
	for(int i=0;i<=x;i++)
	{
		sum=(sum*base+(ull)c[i])%mod;
	 } 
	 return sum;
}
int back_hashs(char c[],int x,int len)
{
	ull sum=0;
	for(int i=len-x-1;i<len;i++)
	{
		sum=(sum*base+(ull)c[i])%mod;
	 } 
	 return sum;
}
int main()
{
	 cin>>a;
	 cin>>b;
	 int n=strlen(a);
	 int m=strlen(b);
	 for(int i=0;i<n;i++)
{
	 firsta[i]=first_hashs(a,i);//求前缀和 
	 backa[i]=back_hashs(a,i,n);//反过来的前缀和 
}
	 for(int i=0;i<m;i++)
	{
	 firstb[i]=first_hashs(b,i);//求前缀和 
	 backb[i]=back_hashs(b,i,m);
}
	 int minm=min(n,m);//取最小长度 
	 int cnt=0;
	 int cnt1=0;
	 for(int i=0;i<minm;i++)
	 {
	 	if(firsta[i]==backb[i])
	 	 cnt=i;//记录a前b后情况 
	 	if(firstb[i]==backa[i])
		 cnt1=i; //记录a后b前情况 
	  }
	 int CNT=max(cnt+1,cnt1+1);
	 cout<<CNT;   
 } 

明天目标:学习map映射。巩固kmp,hash。 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值