文章目录
接着上一章: 四.1链接
标题最多100字还是不够写啊……
标题写这么多是为了自己在找刷题记录的时候方便找到那个博客hh
因为那样一搜就可以搜出来了嘛,毕竟不能直接在自己的博客的关键词搜索栏里搜文章内容……
反正这种笔记当然是方便自己查阅为主嘛……
——2020年11月17日(周二)——————————————————
#搜索
是搜索哒!!! 别看不起搜索,搜索可是很重要的基础能力!
一、排列数字
#include<bits/stdc++.h>
using namespace std;
const int N = 10;
int n;
int a[10];
bool st[10];
void dfs(int x){
//第n个数了!!!
if(x > n){
for(int i = 1; i <= n; i ++) printf("%d ", a[i]);
printf("
");
return ;
}
else {
for(int i = 1; i <= n; i ++) {
if(!st[i]){
st[i] = 1;
a[x] = i;
dfs(x + 1);
st[i] = 0;
}
}
return ;
}
}
int main(){
cin >> n;
dfs(1);
return 0;
}
二、n-皇后
#include<bits/stdc++.h>
using namespace std;
const int N = 20;
int n;
//斜边向上 列, 斜边向下
int dg[N], col[N], g[N];
void dfs(int x){
if(x > n){
//行
for(int i = 1; i <= n; i ++){
//列
for(int j = 1; j <= n; j ++){
if(col[j] == i) printf("Q");
else printf(".");
}
puts("");
}
puts("");
return ;
}
else{
//找到本行所有没有冲突的位置
//这里枚举列
//推导一下那个那个!y = x + b, b = y - x + n(防止为负数);
//y = -x + b, b = x + y;
for(int i = 1; i <= n; i ++){
if(!col[i] && !g[x + i] && !dg[i - x + n]) {
//记下当前列对应的行
col[i] = x, g[x + i] = dg[i - x + n] =1;
dfs(x + 1);
col[i] = g[x + i] = dg[i - x + n] = 0;
}
}
return ;
}
}
int main(){
cin >> n;
dfs(1);
return 0;
}
对比之前的写法:
最大的区别就是我这一次省略了二维字符数组c来储存整个图,省了空间和时间。
除此之外,其它的写法两者是一样的。
#include<bits/stdc++.h>
using namespace std;
const int N = 20;
int n;
bool col[N], dg[N], udg[N];
char c[N][N];
void dfs(int x){
if(x == n){
for(int i = 0; i < n; i ++)
puts (c[i]);
cout << endl;
return ;
}
for(int y = 0; y < n; y ++){
if(!col[y] && !dg[n - x + y] && !udg[x + y]){
col[y] = dg[n - x + y] = udg[x + y] = 1;
c[x][y] = 'Q';
dfs(x + 1);
c[x][y] = '.';
col[y] = dg[y - x + n] = udg[x + y] = 0;
}
}
return ;
}
int main(){
cin >> n;
for(int i = 0; i < n; i ++)
for(int j = 0; j < n; j ++)
c[i][j] = '.';
dfs(0);
return 0;
}
#BFS
三、走迷宫
大意了我没有注意到边界问题;
#include<bits/stdc++.h>
using namespace std;
int n, m;
int mp[110][110];
int a[110][110];
int xadd[4] = {-1, 1, 0, 0}, yadd[4] = {0, 0, -1, 1};
typedef pair<int, int> P;
queue<P> q;
void bfs(){
while(!q.empty()){
P t = q.front();
q.pop();
int xnow = t.first, ynow = t.second;
for(int i = 0; i < 4; i ++){
int xnew = xnow + xadd[i], ynew = ynow + yadd[i];
//可以走且没走过就走 还有边界问题!
if(!mp[xnew][ynew] && !a[xnew][ynew] && xnew >= 1 && xnew <= n && ynew >= 1 && ynew <= m){
a[xnew][ynew] = a[xnow][ynow] + 1;
q.push( {xnew, ynew} );
}
}
}
printf("%d", a[n][m]);
}
int main(){
cin >> n >> m;
for(int i = 1; i <= n; i ++)
for(int j = 1; j <= m; j ++){
scanf("%d", &mp[i][j]);
}
P t = {1, 1};
q.push (t);
bfs();
return 0;
}
再来对比一下过去的,这次我用了STL,相比以前节约了不少空间,时间也快了点。
STL真香!
//这是以前的,参考一下。
#include<bits/stdc++.h>
using namespace std;
const int N = 110;
typedef pair<int , int > P;
int n, m;
int g[N][N], d[N][N];
P q[N * N];
int bfs(){
int hh = 0, tt = 0;
memset(d, -1, sizeof d);
d[0][0] = 0;
q[0] = {0, 0};
int dx[4] = {-1, 1, 0, 0}, dy[4] = {0, 0, -1, 1};
while(hh <= tt){
auto t = q[hh ++];
for(int i = 0 ; i < 4; i ++){
int x = t.first + dx[i], y = t.second + dy[i];
if(x >= 0 && x < n && y >= 0 && y < m && d[x][y] == -1 && !g[x][y] ){
d[x][y] = d[t.first][t.second] + 1;
q[++ tt] = {x, y};
}
}
}
return d[n - 1][m - 1];
}
int main(){
cin >> n >> m;
for(int i = 0 ; i < n; i ++)
for(int j = 0 ; j < m; j ++)
cin >> g[i][j];
cout << bfs();
return 0;
}
四、八数码
这道题很有意思,用到了一个STL中的关联容器unordered_map
无序映射unordered_map
是关联容器,用于存储由键值和映射值的组合形成的元素,并允许根据其键快速检索各个元素。
#include<bits/stdc++.h>
using namespace std;
int bfs(string start){
string end = "12345678x";//定义终点的状态
//要用到一个每个元素为string类型的队列
queue<string> q;
//距离数组,表示对应状态的距离.可以理解为当前字符串对应一个整数类型的距离
unordered_map<string, int> d;
//将start 放到队列里面做起点
q.push (start);
//起点的距离是0
d[start] = 0;
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
//深搜开始
while(q.size()){
//t是当前状态啦!
string t = q.front();
q.pop();
int distance = d[t];
//假设到终点就结束
if(t == end) return distance;
//状态转移
//返回x的下标
//用int 类型来获取find()的值的话,会获得所在下标.
int k = t.find('x');
//找到x的横、纵坐标,类似解码的操作
int x = k / 3, y = k % 3;
for(int i = 0; i < 4; i ++){
//找到下一个状态的横、纵坐标
int a = x + dx[i], b = y + dy[i];
//判断下一个位置的边界
if(a >= 0 && a< 3 && b >= 0 && b < 3){
//swap可以交换t里面两个位置上的值,也就是对应的两者交换了位置,超级方便的。
swap(t[k], t[a * 3 + b]);
//如果更新完的t没有被搜到过的话,那么我们就找到了一个新的状态
//count() 统计元素出现次数!
if(!d.count(t)){
//更新t的距离
d[t] = distance + 1;
//将t入队
q.push(t);
}
//别忘了返回状态
swap(t[k], t[a * 3 + b]);
}
}
}
//没找到终点
return -1;
}
int main(){
string start;
//输入字符串
for(int i = 0; i < 9; i ++){
char c;
cin >> c;
start += c;
}
//bfs求距离
cout << bfs(start) << endl;
return 0;
}
——2020年11月18日(周三)——————————————————
肝文化课去了……
——2020年11月19日(周四)——————————————————
双指针
小试牛刀模板题:
判断子序列
一、判断子序列
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int a[N], b[N];
int main(){
int n, m;
cin >> n >> m;
for(int i = 0; i < n; i ++) scanf("%d", &a[i]);
for(int i = 0; i < m; i ++) scanf("%d", &b[i]);
int i = 0, j = 0;
//我在考虑要不要加上 && i < n,虽说不加也能过就是了。但是会不会不严谨?
//好吧,还真是不严谨,随便出个数据就出错了……果然两个指针的范围都要明确一下
while(j < m && i < n){
if(a[i] == b[j]) i ++;
j ++;
}
if(i == n ) printf("Yes");
else printf("No");
return 0;
}
堆
二、模拟堆
题目链接
比平常用的堆要复杂一点点……就一点。
主要是多了要修改或删除第k个计入的元素,这就需要另开三个数组来储存三种属性……
#include <iostream>
#include <algorithm>
#include <string.h>
using namespace std;
const int N = 100010;
//当前堆序列第n个数的数值,编号是m的点在堆中对应的位置,堆中对应位置的点的编号, 点数
//好绕口……不过为了题目需要没办法……
int h[N], ph[N], hp[N], cnt;
//将两个点的一切信息互换……
void heap_swap(int a, int b)
{
//注意这里a, b是指两个数在堆序列中对应的序号
//先是将两个位置上点编号对应的堆中位置互换
swap(ph[hp[a]],ph[hp[b]]);
//然后是两个位置对应的点编号互换
swap(hp[a], hp[b]);
//最后就是两个堆中的位置互换
swap(h[a], h[b]);
}
//真的是好绕好绕啊!!!脑子都打结了……虽然脑子本来就像打结了的那样
//下沉过程……
void down(int u)
{
//先用t指向u
int t = u;
//然后找到最小的子节点
if (u * 2 <= cnt && h[u * 2] < h[t]) t = u * 2;
if (u * 2 + 1 <= cnt && h[u * 2 + 1] < h[t]) t = u * 2 + 1;
//如果找到了就往那个子节点
if (u != t)
{
//先进行各种交换
heap_swap(u, t);
//下沉
down(t);
}
}
//上浮
void up(int u)
{
while (u / 2 && h[u] < h[u / 2])
{
heap_swap(u, u / 2);
u >>= 1;
}
}
int main()
{
int n, m = 0;
scanf("%d", &n);
while (n -- )
{
char op[5];
int k, x;
scanf("%s", op);
//这是比较函数,如果两者一致就会返回0...因为如果a比b的值小会返回<0,反之返回>0.
//插入
if (!strcmp(op, "I"))
{
//插入的数值
scanf("%d", &x);
//当前点的个数
cnt ++ ;
//插入数值的对应新编号
m ++ ;
//以下是新开一个节点
//ph是新当前编号指向的节点在堆序列里面的序数
//hp相对的要指回来这个节点独一无二的编号
//h就是当前堆序列里面第cnt个数的值
ph[m] = cnt, hp[cnt] = m, h[cnt] = x;
//然后从堆底往上浮
up(cnt);
}
//输出最小值,最小值就是堆顶的值
else if (!strcmp(op, "PM")) printf("%d
", h[1]);
//弹出堆顶
else if (!strcmp(op, "DM"))
{
//先将堆顶和堆底的元素交换所有,
heap_swap(1, cnt);
//然后舍弃最后一个元素,相当于弹出
cnt -- ;
//堆顶下沉重新排序整个堆
down(1);
}
else if (!strcmp(op, "D"))
{
//这就是三个单开数组储存那么多信息的目的:为了可以删除或者修改第k个插入的数!
scanf("%d", &k);
//这里直接赋值为在堆序列中对应的位置
k = ph[k];
//将对应位置上的点和最后一个元素进行交换
heap_swap(k, cnt);
//然后像飞船升空后抛弃燃料用完了的提速火箭一样将堆底弹出
cnt -- ;
//由于k的位置很可能会在中间,所以要先上浮到堆顶,然后下沉到堆底。
//为了保证不破坏堆的结构,就让新的第K个元素向上然后再向下
//具体情况有很多,这样做百分百没错……
up(k);
down(k);
}
else
{
//这里是要修改第K个插入的值,同样是修改后先上浮后下沉……
scanf("%d%d", &k, &x);
k = ph[k];
h[k] = x;
up(k);
down(k);
}
}
return 0;
}
——2020年11月20日(周五)——————————————————
对线文化科%…
——2020年11月21日(周六)——————————————————
字符串哈希
一、字符串哈希
题目
阿巴阿巴阿巴……
可以理解为以字符为基础的前缀和哈希。
将字符串表示为一个P进制的数,然后用公式h[r] - h[l - 1] * p[r - l + 1];
求得所求字符串对应的哈希值;
一样的哈希值就是一样的字符串咯~
#include<bits/stdc++.h>
using namespace std;
typedef unsigned long long ULL;
const int N = 1e5 + 10, P = 131; // || P = 13331
char s[N];
ULL p[N];
ULL h[N];
ULL get(int l, int r){
return h[r] - h[l - 1] * p[r - l + 1];
}
int main(){
p[0] = 1;
int n, m;
cin >> n >> m;
scanf("%s", s + 1);
for(int i = 1; i <= n; i ++){
p[i] = p[i - 1] * P;
h[i] = h[i - 1] * P + s[i];
}
int l1, r1, l2, r2;
for(int i = 1; i <= m; i ++){
scanf("%d%d%d%d", &l1, &r1, &l2, &r2);
if(get(l1, r1) == get(l2, r2)) printf("Yes
");
else printf("No
");
}
return 0;
}