hash表主要包括以下基本操作
1.计算hash函数的值
2.定位到对应链表中依次遍历,比较。
时间限制: 1.00s 内存限制: 512.00MB
复制 Markdown
中文
退出 IDE 模式
你可能听说过没有两片雪花是相同的。你的任务是编写一个程序来确定这是否真的是正确的。你的程序将读取关于一组雪花的信息,并搜索可能相同的一对雪花。每片雪花有六条“角”。对于每片雪花,你的程序将提供每条角的长度测量。任何一对长度对应的角相同的雪花都应该被你的程序标记为可能相同。
输入的第一行包含一个整数 n,0<n≤100000,表示接下来的雪花数量。接下来的 n 行描述每片雪花。每片雪花由包含六个整数的一行描述(每个整数至少为 0 且小于 10000000),表示雪花的六条角的长度。角的长度将按顺序围绕着雪花给出(顺时针或逆时针),但它们可以从六个角中的任何一条开始。例如,同一片雪花可以描述为 1 2 3 4 5 6 或 4 3 2 1 6 5。
如果所有的雪花都是不同的,你的程序应该打印消息:
No two snowflakes are alike.
如果有一对可能相同的雪花,你的程序应该打印消息:
Twin snowflakes found.
翻译来自于:ChatGPT
显示翻译
输入 #1复制运行
2 1 2 3 4 5 6 4 3 2 1 6 5
输出 #1复制运行
Twin snowflakes found.
定义一个hash函数 计算hash值为六个数字之和加六个数字之积 对于相同的雪花他们的hash值一定相同 建立一个hash表 将雪花依次插入 对于每片雪花 我们都扫描表头对应的链表 检查是否存在相同的雪花 相同输出 结束程序 不同则插入 继续进行知道结束
每个head指向每个hash值的一个头元素 然后他的next存储了这个链表(这个hash值)的其他雪花 通head找到头 然后通过next访问整个链表 每次插入的时候插入到链表的头部 然后更新next
#include <bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int n,tot,snow[N][6],head[N],nex[N];
const int mod =99991;
int H(int *a){
int sum=0,mul=1;
for(int i=0;i<6;i++){
sum=(sum+a[i])%mod;
mul=1LL*mul*a[i]%mod;
}
return (sum+mul)%mod;
}
bool equal(int *a,int *b){
for(int i=0;i<6;i++){
for(int j=0;j<6;j++){
bool eq=1;
for(int k=0;k<6;k++)
if(a[(i+k)%6]!=b[(j+k)%6])eq=0;
if(eq)return 1;
eq=1;
for(int k=0;k<6;k++)
if(a[(i+k)%6]!=b[(j-k+6)%6])eq=0;
if(eq)return 1;
}
}
return 0;
}
bool insert(int *a){
int val =H(a);
for(int i=head[val];i;i=nex[i]){
if(equal(snow[i],a))return 1;
}
++tot;
memcpy(snow[tot],a,6*sizeof(int));
nex[tot]=head[val];
head[val]=tot;
return 0;
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin>>n;
for(int i=1;i<=n;i++){
int a[10];
for(int j=0;j<6;j++)cin>>a[j];
if(insert(a)){
cout<<"Twin snowflakes found."<<endl;
return 0;
}
}
cout<<"No two snowflakes are alike.";
return 0;
}
还有一种最小表示法的优化方法 暂时未给出
字符串hash把一个任意长度的字符串映射成一个非负整数 并且冲突的概率几乎为 0;
取一个固定值p,把字符串看作为一个p进制数并且分配一个大于0 的数值 ,代表每种字符;一般来说我们分配的数值都远小于p 例如小写字母构成的字符串 可以令a-z分别为1到26 取一个固定值M求出该p进制数对M的余数作为hash值
一般来说 p取131 13331 此时hash值产生冲突的概率极低 通常我们取一个较大的质数为M 或者取M=2^64 也就是usigned long long 在计算不处理算数溢出的问题的时候 产生溢出直接对这个取模 可以避免低效的取模运算;
对于字符串的各种操作 我们都可以反应到hash值来
如果hash值为h(s) 那么在字符串s后面加上字符c 他的hash值即为H(s+c)=(H(s)*p+value[c])mod M 也就是进制左移 加上c代表的数值
如果知道了s的hash和s+t的hash值 那么我们也可以求t的hash值 : H(T)=(H(s+t)-H(s)*p^length(t)) mod M 。 相当于在p进制下左移s的hash值使得s与s+t的左端对齐 那么相减后就是t的hash值
计算某一段的hash值:
首先 最暴力的也就是从这一段的起始遍历到末尾 然后模拟进制 但是很暴力 很容易导致tle
long long H(int l,int r){
long long ans=0;
for(int i=l;l<=r;l++){
ans=(ans*p+(s[i]-'a'))%mod;
}
return ans;
}
第二种做法
预处理后运用类似于前缀和的思想进行计算:
ULL H(int l,int r){ //获得l r 段的hash值 r段的hash减去l-1段的hash移位 移动到与r最高位对齐
return h[r] - h[l - 1] * power[r - l + 1]; //移动后的总长度为r所以后段与进位的索引和也为r
}
//预处理
char str[N];
scanf("%s",str+1);
int n=strlen(str+1);
power[0]=1;
for(int i=1;i<=n;i++){
h[i]=h[i-1]*p+str[i]-'a'+1; //计算从开头到当前的字符的hash
power[i]=power[i-1]*p; //计算进位
}
时间限制: 1.00s 内存限制: 512.00MB
复制 Markdown 退出 IDE 模式
很久很久以前,森林里住着一群兔子。
有一天,兔子们想要研究自己的 DNA 序列。
我们首先选取一个好长好长的 DNA 序列(小兔子是外星生物,DNA 序列可能包含 26 个小写英文字母)。
然后我们每次选择两个区间,询问如果用两个区间里的 DNA 序列分别生产出来两只兔子,这两个兔子是否一模一样。
注意两个兔子一模一样只可能是他们的 DNA 序列一模一样。
第一行输入一个 DNA 字符串 S。
第二行一个数字 m,表示 m 次询问。
接下来 m 行,每行四个数字 l1,r1,l2,r2,分别表示此次询问的两个区间,注意字符串的位置从 1 开始编号。
对于每次询问,输出一行表示结果。
如果两只兔子完全相同输出
Yes,否则输出
No(注意大小写)。
输入 #1复制运行
aabbaabb 3 1 3 5 7 1 3 6 8 1 2 1 2
输出 #1复制运行
Yes No Yes
数据保证,1≤∣S∣,m≤106。其中,∣S∣ 为字符串 S 的长度。
#include <bits/stdc++.h>
using namespace std;
typedef unsigned long long ULL;
int p=131;
const int mod =23333,N=1e6+5;
ULL h[N], power[N];
char str[N];
ULL H(int l,int r){ //获得l r 段的hash值 r段的hash减去l-1段的hash移位 移动到与r最高位对齐
return h[r] - h[l - 1] * power[r - l + 1]; //移动后的总长度为r所以后段与进位的索引和也为r
}
int main() {
scanf("%s",str+1);
int n=strlen(str+1);
power[0]=1;
int m;cin>>m;
for(int i=1;i<=n;i++){
h[i]=h[i-1]*p+str[i]-'a'+1; //计算从开头到当前的字符的hash
power[i]=power[i-1]*p; //计算进制
}
for(int i=1,l1,r1,l2,r2;i<=m;i++){
cin>>l1>>r1>>l2>>r2;
if(H(l1,r1)==H(l2,r2))cout<<"Yes
";
else cout<<"No
";
}
return 0;
}
时间限制: 1.00s 内存限制: 125.00MB
复制 Markdown
中文
退出 IDE 模式
据说如果你给无限只母牛和无限台巨型便携式电脑(有非常大的键盘 ), 那么母牛们会制造出世上最棒的回文。你的工作就是去寻找这些牛制造的奇观(最棒的回文)。
在寻找回文时不用理睬那些标点符号、空格(但应该保留下来以便做为答案输出), 只用考虑字母 A∼Z 和 a∼z。要你寻找的最长的回文的文章是一个不超过 20,000 个字符的字符串。我们将保证最长的回文不会超过 2,000 个字符(在除去标点符号、空格之前)。
输入文件不会超过 20,000 字符。这个文件可能一行或多行,但是每行都不超过 80 个字符(不包括最后的换行符)。
输出的第一行应该包括找到的最长的回文的长度。
下一行或几行应该包括这个回文的原文(没有除去标点符号、空格),把这个回文输出到一行或多行(如果回文中包括换行符)。
如果有多个回文长度都等于最大值,输出最前面出现的那一个。
输入 #1复制运行
Confucius say: Madam, I'm Adam.
输出 #1复制运行
11 Madam, I'm Adam
题目翻译来自NOCOW。
USACO Training Section 1.3
朴素算法 直接枚举每个位置 为中心 然后向两边延申 相同的话就长度加2 记录字符串的起始和末尾然后输出即可 缺点就是时间复杂度比较高
#include <bits/stdc++.h>
using namespace std;
const int N=2e4+10;
int n,l,at[N],last; //at保存新的字符在原来的字符的映射;
char k[N],m[N];
int get(int n){
int a1=1,a2=0;
for(int i=n,j=1;i-j>=0&&i+j<l&&m[i-j]==m[i+j];j++)a1+=2;
for(int i=n,j=0;i-j>=0&&i+1+j<l&&m[i-j]==m[i+j+1];j++)a2+=2;
return max(a1,a2);
}
int main() {
while((k[n]=getchar())!=EOF)n++;
for(int i=0;i<n;i++){
if(isalpha(k[i])){
m[l]=tolower(k[i]);at[l++]=i;
}
}
int ans=0;
for(int i=0;i<l;i++){
int t=get(i);
if(t>ans){
ans=t;
last=i+(t/2);
}
}
k[at[last]+1]='