刷题周记(四.2)——#DFS:排列数字、n-皇后#BFS:走迷宫、八数码#双指针:判断子序列#堆:模拟堆#字符串哈希


接着上一章: 四.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;
}

——(完)———————————————————————————

原文地址:https://www.cnblogs.com/yuanyulin/p/14026708.html