【哈希专题】【蓝桥杯备考训练】:星空之夜、模拟散列表、字符串哈希、四平方和、扫雷【已更新完成】

本文主要是介绍【哈希专题】【蓝桥杯备考训练】:星空之夜、模拟散列表、字符串哈希、四平方和、扫雷【已更新完成】,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

1、星空之夜(usaco training 5.1)

2、模拟散列表(模板)

3、字符串哈希(模板)

4、四平方和(第七届蓝桥杯省赛C++ A组/B组 & JAVA B组/C组)

5、扫雷(Google Kickstart2014 Round C Problem A)


1、星空之夜(usaco training 5.1)

夜空深处,闪亮的星星以星群的形式出现在人们眼中,形态万千。

一个星群是指一组非空的在水平,垂直或对角线方向相邻的星星的集合。

一个星群不能是一个更大星群的一部分。

星群可能是相似的。

如果两个星群的形状、包含星星的数目相同,那么无论它们的朝向如何,都认为它们是相似的。

通常星群可能有 8 种朝向,如下图所示:

starry-1.gif

现在,我们用一个二维 01 矩阵来表示夜空,如果一个位置上的数字是 1,那么说明这个位置上有一个星星,否则这个位置上的数字应该是 0。

给定一个夜空二维矩阵,请你将其中的所有星群用小写字母进行标记,标记时相似星群用同一字母,不相似星群用不同字母。

标注星群就是指将星群中所有的 1 替换为小写字母。

输入格式

第一行包含一个整数 W,表示矩阵宽度。

第二行包含一个整数 H,表示矩阵高度。

接下来 H 行,每行包含一个长度为 W 的 01 序列,用来描述整个夜空矩阵。

输出格式

输出标记完所有星群后的二维矩阵。

用小写字母标记星群的方法很多,我们将整个输出读取为一个字符串,能够使得这个字符串字典序最小的标记方式,就是我们想要的标记方式。

输出这个标记方式标出的最终二维矩阵。

数据范围

0≤W,H≤1000
0≤ 星群数量 ≤500
0≤ 不相似星群数量 ≤26
1≤星群中星星的数量 ≤160

输入样例:
23
15
10001000000000010000000
01111100011111000101101
01000000010001000111111
00000000010101000101111
00000111010001000000000
00001001011111000000000
10000001000000000000000
00101000000111110010000
00001000000100010011111
00000001110101010100010
00000100110100010000000
00010001110111110000000
00100001110000000100000
00001000100001000100101
00000001110001000111000
输出样例:
a000a0000000000b0000000
0aaaaa000ccccc000d0dd0d
0a0000000c000c000dddddd
000000000c0b0c000d0dddd
00000eee0c000c000000000
0000e00e0ccccc000000000
b000000e000000000000000
00b0f000000ccccc00a0000
0000f000000c000c00aaaaa
0000000ddd0c0b0c0a000a0
00000b00dd0c000c0000000
000g000ddd0ccccc0000000
00g0000ddd0000000e00000
0000b000d0000f000e00e0b
0000000ddd000f000eee000
样例解释

样例对应的星空图如下:

starry-2.gif

答案对应的标记后星空图如下:

starry-3.gif

思路:

用欧几里得距离来辨别形状是否相同,用一个变量 id 来记录用到了第几个字母,top来记录连通块中点的数量,进行字母填充

代码:
//欧几里得距离 
//不能保证完全不相同,但能保证大概率不相同 
#include<bits/stdc++.h>using namespace std;const int N=101;
const double eps=1e-6;int n,m;typedef pair<int,int> PII;char g[N][N];
PII q[N*N];
int top;//表示当前连通块有多少个点 double get_dis(PII a,PII b)
{double dx=a.first - b.first;double dy=a.second - b.second;return sqrt(dx*dx+dy*dy);
}double get_hash()
{double sum=0;for(int i=0;i<top;i++)for(int j=i+1;j<top;j++){sum+=get_dis(q[i],q[j]) ;}return sum;
}char get_id(double key)
{static double hash[30];//static 等价于全局变量 (每次调用函数,数组不会重新分配) static char id=0;for(int i=0;i<id;i++){if(fabs(hash[i]-key)<eps){return i+'a';}} hash[id++]=key;return id-1+'a';
}void dfs(int a,int b)
{q[top++]={a,b};g[a][b]='0';for(int x=a-1;x<=a+1;x++)for(int y=b-1;y<=b+1;y++){if(x==a && y==b)continue;if(x>=0 * y>=0 && x<n && y<m && g[x][y]=='1'){dfs(x,y);}}}//flood fillint main()
{cin>>m>>n;for(int i=0;i<n;i++){cin>>g[i]; }for(int i=0;i<n;i++)for(int j=0;j<m;j++){if(g[i][j]=='1'){top=0;dfs(i,j);char c=get_id(get_hash());//cout<<c;for(int k=0;k<top;k++){//cout<<c<<endl;g[q[k].first][q[k].second]=c;}}}for(int i=0;i<n;i++)cout<<g[i]<<endl;return 0;
} 

2、模拟散列表(模板)

维护一个集合,支持如下几种操作:

  1. I x,插入一个整数 x;
  2. Q x,询问整数 x 是否在集合中出现过;

现在要进行 N 次操作,对于每个询问操作输出对应的结果。

输入格式

第一行包含整数 N,表示操作数量。

接下来 N 行,每行包含一个操作指令,操作指令为 I xQ x 中的一种。

输出格式

对于每个询问指令 Q x,输出一个询问结果,如果 x 在集合中出现过,则输出 Yes,否则输出 No

每个结果占一行。

数据范围

1≤N≤1e5
−1e9≤x≤1e9

输入样例:
5
I 1
I 2
I 3
Q 2
Q 5
输出样例:
Yes
No
思路:

散列表的标准写法

代码:
//1e9+7这是一个质数
#include<bits/stdc++.h>using namespace std;//开放寻址发一般开成两倍
//找出200000之后第一个质数
/*
for(int i=2*1e5;;i++)
{bool flag=true;for(int j=2;j*j<=i;j++){if(i%j==0){flag=false;break;}}if(flag){cout<<i;break;}
}
*/ const int N=200003,null=0x3f3f3f3f;
//一般设最大值都是0x3f3f3f3f,大约比1e9大一些 
//用null这个数表示位置上没有数字,null这个数字在题目给的数字范围之外,所以说不会引起错误
//如果设置的数在数字范围之内,可能会造成错误判断(本来没有这个数结果每个空位置都是这个数) 
int h[N];//开放寻址法
int find(int x)
{int k=(x%N+N)%N;while(h[k]!=null && h[k]!=x){k++;if(k==N)k=0;//后面都没有就从头开始找}	return k;
}void insert(int x)
{int t=find(x);h[t]=x;
}int main()
{int n;cin>>n;memset(h,0x3f,sizeof h);//memset是按照字节来set的,//比如说int是四个字节,则每个字节都是0x3f,即为0x3f3f3f3fwhile(n--){char op[2];int x;cin>>op;cin>>x;if(*op=='I'){insert(x);}else{int k=find(x);if(h[k]!=null) cout<<"Yes"<<endl;else cout<<"No"<<endl;}}return 0;
}

3、字符串哈希(模板)

给定一个长度为 n 的字符串,再给定 m 个询问,每个询问包含四个整数 l1,r1,l2,r2,请你判断 [l1,r1]和 [l2,r2]这两个区间所包含的字符串子串是否完全相同。

字符串中只包含大小写英文字母和数字。

输入格式

第一行包含整数 n 和 m,表示字符串长度和询问次数。

第二行包含一个长度为 n 的字符串,字符串中只包含大小写英文字母和数字。

接下来 m 行,每行包含四个整数 l1,r1,l2,r2,表示一次询问所涉及的两个区间。

注意,字符串的位置从 1 开始编号。

输出格式

对于每个询问输出一个结果,如果两个字符串子串完全相同则输出 Yes,否则输出 No

每个结果占一行。

数据范围

1≤n,m≤1e5

输入样例:
8 3
aabbaabb
1 3 5 7
1 3 6 8
1 2 1 2
输出样例:
Yes
No
Yes
思路:

字符串哈希模板写法

代码:
#include<bits/stdc++.h>
using namespace std;int n,m;typedef unsigned long long  ULL;const int N=100010,P=131;char str[N];
ULL h[N],p[N];//不能映射成0 
//假定不存在冲突的情况
//当p取131或1331的时候(这两个是经验值),q取成2的64次方,可以假定不会出现冲突(99.99%的情况下不会冲突)
//h[lr]=h[r]-h[l] * P**(l-r+1),为l到r之间的哈希值 
//9999 - 97*100
ULL get(int l,int r)
{return h[r]-h[l-1]*p[r-l+1];
}int main()
{cin>>n>>m>>str+1;//预处理p[0]=1;for(int i=1;i<=n;i++)p[i]=p[i-1]*P; //每个位置的权重求出来 即为p的次方 for(int i=1;i<=n;i++)h[i]=h[i-1]*P + str[i];while(m--){int l1,r1,l2,r2;cin>>l1>>r1>>l2>>r2;if (get(l1,r1)==get(l2,r2))cout<<"Yes"<<endl;else cout<<"No"<<endl; }	return 0;
}

4、四平方和(第七届蓝桥杯省赛C++ A组/B组 & JAVA B组/C组)

四平方和定理,又称为拉格朗日定理:

每个正整数都可以表示为至多 4 个正整数的平方和。

如果把 0 包括进去,就正好可以表示为 4个数的平方和。

比如:

5=0**2+0**2+1**2+2**2
7=1**2+1**2+1**2+2**2

对于一个给定的正整数,可能存在多种平方和的表示法。

要求你对 4 个数排序:

0≤a≤b≤c≤d0

并对所有的可能表示法按 a,b,c,d 为联合主键升序排列,最后输出第一个表示法。

输入格式

输入一个正整数 N。

输出格式

输出4个非负整数,按从小到大排序,中间用空格分开。

数据范围

0<N<5∗1e6

输入样例:
5
输出样例:
0 0 1 2
思路:

哈希加二分

代码:
//四平方和
//二分
#include<bits/stdc++.h>using namespace std;const int N = 5e6 + 10;struct Sum{int s, c, d;bool operator<(const Sum &t)const{if(s != t.s) return s < t.s;if(c != t.c) return c < t.c;return d < t.d;}
};int n;
Sum record[N * 2];int main()
{cin >> n;int k = 0;for(int c = 0; c * c <= n; c++){for(int d = c; c * c + d * d <= n; d ++){record[k++] = {c * c + d * d, c, d};}}sort(record, record + k);for(int a = 0; a * a <= n; a ++){for(int b = a; a * a + b * b <= n; b++){int t = n - a * a - b * b;int l = 0, r = k - 1;while(l < r){int mid = l + r >> 1;if(record[mid].s >= t) r = mid;else l = mid + 1;}if(record[l].s == t){printf("%d %d %d %d\n", a, b, record[l].c, record[l].d);return 0;}}}return 0;
}

5、扫雷(Google Kickstart2014 Round C Problem A)

扫雷是一种计算机游戏,在 20 世纪 80 年代开始流行,并且仍然包含在某些版本的 Microsoft Windows 操作系统中。

在这个问题中,你正在一个矩形网格上玩扫雷游戏。

最初网格内的所有单元格都呈未打开状态。

其中 M 个不同的单元格中隐藏着 M 个地雷。

其他单元格内不包含地雷。

你可以单击任何单元格将其打开。

如果你点击到的单元格中包含一个地雷,那么游戏就会判定失败。

如果你点击到的单元格内不含地雷,则单元格内将显示一个 0 到 8 之间的数字(包括 0 和 8),这对应于该单元格的所有相邻单元格中包含地雷的单元格的数量。

如果两个单元格共享一个角或边,则它们是相邻单元格。

另外,如果某个单元格被打开时显示数字 0,那么它的所有相邻单元格也会以递归方式自动打开。

当所有不含地雷的单元格都被打开时,游戏就会判定胜利。

例如,网格的初始状态可能如下所示(* 表示地雷,而 c 表示第一个点击的单元格):

*..*...**.
....*.....
..c..*....
........*.
..........

被点击的单元格旁边没有地雷,因此当它被打开时显示数字 00,并且它的 88 个相邻单元也被自动打开,此过程不断继续,最终状态如下:

*..*...**.
1112*.....
00012*....
00001111*.
00000001..

此时,仍有不包含地雷的单元格(用 . 字符表示)未被打开,因此玩家必须继续点击未打开的单元格,使游戏继续进行。

你想尽快赢得游戏胜利并希望找到赢得游戏的最低点击次数。

给定网格的尺寸(N×N),输出能够获胜的最小点击次数。

输入格式

第一行包含整数 T,表示共有 T 组测试数据。

每组数据第一行包含整数 N,表示游戏网格的尺寸大小。

接下来 N 行,每行包含一个长度为 N 的字符串,字符串由 .(无雷)和 *(有雷)构成,表示游戏网格的初始状态。

输出格式

每组数据输出一个结果,每个结果占一行。

结果表示为 Case #x: y,其中 x 是组别编号(从 1 开始),y 是获胜所需的最小点击次数。

数据范围

1≤T≤100
1≤N≤300

输入样例:
2
3
..*
..*
**.
5
..*..
..*..
.*..*
.*...
.*...
输出样例:
Case #1: 2
Case #2: 8
思路:

预处出每个点的数字再对0的位置进行搜索,最后对漏网之鱼进行处理

代码:
#include<bits/stdc++.h>
using namespace std;
const int N =310;
int dx[8]={1,1,1,0,0,-1,-1,-1},dy[8]={1,0,-1,1,-1,1,0,-1};
int num[N][N],st[N][N];
char g[N][N];    //记录扫雷整张地图
void bfs(int a,int b,int n)   //寻找0点周围的所有点,标记为已遍历
{st[a][b]=1;num[a][b]=-1;for(int i=0;i<8;i++){int x=a+dx[i],y=b+dy[i];if(x>=0 && x<n && y>=0 && y<n){if(!st[x][y] && num[x][y]!=-1){if(num[x][y]>0) st[x][y]=1,num[x][y]=-1;if(num[x][y]==0) bfs(x,y,n);}}}
}
int main()
{int T,n;scanf("%d",&T);for(int t=1;t<=T;t++){scanf("%d",&n);for(int i=0;i<n;i++)scanf("%s",g[i]);memset(num,0,sizeof num);for(int i=0;i<n;i++){for(int j=0;j<n;j++){if(g[i][j]!='*')    //不是雷的要统计8个点上雷的总个数{for(int k=0;k<8;k++){int x=i+dx[k],y=j+dy[k];if(x>=0 && x<n && y>=0 && y<n && g[x][y]=='*'){num[i][j]++;}}}else num[i][j]=-1;   //如果是雷则标记为-1}}memset(st,0,sizeof st);int ans=0;for(int i=0;i<n;i++){for(int j=0;j<n;j++){if(num[i][j]==0 && !st[i][j])     //找到一个0点ans++,同时进行bfs{ans++;  bfs(i,j,n);}}}for(int i=0;i<n;i++){for(int j=0;j<n;j++)if(num[i][j]!=-1 && !st[i][j]) ans++;  //找到漏网之鱼,每找到一个就ans++}printf("Case #%d: %d\n",t,ans);}return 0;
}

这篇关于【哈希专题】【蓝桥杯备考训练】:星空之夜、模拟散列表、字符串哈希、四平方和、扫雷【已更新完成】的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/845918

相关文章

Java 字符数组转字符串的常用方法

《Java字符数组转字符串的常用方法》文章总结了在Java中将字符数组转换为字符串的几种常用方法,包括使用String构造函数、String.valueOf()方法、StringBuilder以及A... 目录1. 使用String构造函数1.1 基本转换方法1.2 注意事项2. 使用String.valu

Redis缓存问题与缓存更新机制详解

《Redis缓存问题与缓存更新机制详解》本文主要介绍了缓存问题及其解决方案,包括缓存穿透、缓存击穿、缓存雪崩等问题的成因以及相应的预防和解决方法,同时,还详细探讨了缓存更新机制,包括不同情况下的缓存更... 目录一、缓存问题1.1 缓存穿透1.1.1 问题来源1.1.2 解决方案1.2 缓存击穿1.2.1

Python实现将实体类列表数据导出到Excel文件

《Python实现将实体类列表数据导出到Excel文件》在数据处理和报告生成中,将实体类的列表数据导出到Excel文件是一项常见任务,Python提供了多种库来实现这一目标,下面就来跟随小编一起学习一... 目录一、环境准备二、定义实体类三、创建实体类列表四、将实体类列表转换为DataFrame五、导出Da

Linux Mint Xia 22.1重磅发布: 重要更新一览

《LinuxMintXia22.1重磅发布:重要更新一览》Beta版LinuxMint“Xia”22.1发布,新版本基于Ubuntu24.04,内核版本为Linux6.8,这... linux Mint 22.1「Xia」正式发布啦!这次更新带来了诸多优化和改进,进一步巩固了 Mint 在 Linux 桌面

python修改字符串值的三种方法

《python修改字符串值的三种方法》本文主要介绍了python修改字符串值的三种方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学... 目录第一种方法:第二种方法:第三种方法:在python中,字符串对象是不可变类型,所以我们没办法直接

python安装完成后可以进行的后续步骤和注意事项小结

《python安装完成后可以进行的后续步骤和注意事项小结》本文详细介绍了安装Python3后的后续步骤,包括验证安装、配置环境、安装包、创建和运行脚本,以及使用虚拟环境,还强调了注意事项,如系统更新、... 目录验证安装配置环境(可选)安装python包创建和运行Python脚本虚拟环境(可选)注意事项安装

SpringCloud配置动态更新原理解析

《SpringCloud配置动态更新原理解析》在微服务架构的浩瀚星海中,服务配置的动态更新如同魔法一般,能够让应用在不重启的情况下,实时响应配置的变更,SpringCloud作为微服务架构中的佼佼者,... 目录一、SpringBoot、Cloud配置的读取二、SpringCloud配置动态刷新三、更新@R

JAVA中整型数组、字符串数组、整型数和字符串 的创建与转换的方法

《JAVA中整型数组、字符串数组、整型数和字符串的创建与转换的方法》本文介绍了Java中字符串、字符数组和整型数组的创建方法,以及它们之间的转换方法,还详细讲解了字符串中的一些常用方法,如index... 目录一、字符串、字符数组和整型数组的创建1、字符串的创建方法1.1 通过引用字符数组来创建字符串1.2

C#中字符串分割的多种方式

《C#中字符串分割的多种方式》在C#编程语言中,字符串处理是日常开发中不可或缺的一部分,字符串分割是处理文本数据时常用的操作,它允许我们将一个长字符串分解成多个子字符串,本文给大家介绍了C#中字符串分... 目录1. 使用 string.Split2. 使用正则表达式 (Regex.Split)3. 使用

Ubuntu 24.04 LTS怎么关闭 Ubuntu Pro 更新提示弹窗?

《Ubuntu24.04LTS怎么关闭UbuntuPro更新提示弹窗?》Ubuntu每次开机都会弹窗提示安全更新,设置里最多只能取消自动下载,自动更新,但无法做到直接让自动更新的弹窗不出现,... 如果你正在使用 Ubuntu 24.04 LTS,可能会注意到——在使用「软件更新器」或运行 APT 命令时,