zoukankan      html  css  js  c++  java
  • 刷题周记(四.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;
    }
    

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

  • 相关阅读:
    NOIP201* 游记
    [Luogu3378] 【模板】堆 题解
    [BZOJ5105]【[Code+#1]晨跑】 题解
    牛客多校第九场 E All men are brothers 并查集/组合论
    牛客多校第九场 D Knapsack Cryptosystem 背包
    牛客多校第九场 B Quadratic equation 模平方根
    hdu多校第八场 1011 (hdu6667) Roundgod and Milk Tea 二分图匹配
    hdu多校第八场 1010(hdu6666) Quailty and CCPC 排序/签到
    hdu多校第八场 1003 (hdu6659) Acesrc and Good Numbers 数论/打表
    hdu多校第八场 1009 (hdu6665) Calabash and Landlord 计算几何/dfs
  • 原文地址:https://www.cnblogs.com/yuanyulin/p/14026708.html
Copyright © 2011-2022 走看看