0
点赞
收藏
分享

微信扫一扫

算法例题——暴力枚举、dfs、回溯、排序、其他

四月Ren间 2022-03-25 阅读 75
c++算法

目录

 一、暴力枚举例题

 1.蓝桥杯2015A2奇妙的数字

 2.蓝桥杯2015A7手链样式

 3.蓝桥杯2016A8四平方和

 二、dfs 

 1.蓝桥杯2015A6牌型总数

 2.蓝桥杯2017A1迷宫

  三、回溯

 1.蓝桥杯2016A3方格填数

  2.蓝桥杯2016A7剪邮票

 四、排序

 1.蓝桥杯2016A4快速排序

 五、其他

 1.蓝桥杯2016A6消除尾一


持续更新中...

 一、暴力枚举例题

 1.蓝桥杯2015A2奇妙的数字

小明发现了一个奇妙的数字。
它的平方和立方正好把0~9的10个数字每个用且只用了一次。
你能猜出这个数字是多少吗?

#include <iostream>
#include <string>
#include <sstream>
#include <set>
using namespace std;

// 整型转换为字符型
void i2s(int num, string &str) {
    stringstream ss;
    ss << num;
    ss >> str;
}

bool check(string s) {
    set<char> ss;

    for (int i = 0; i < s.length(); i++) {
        ss.insert(s[i]); // 向集合中插入数字,数字无重复
    }
    return s.size() == 10 && ss.size() == 10; // 平方和立方的字符串相连长度为10且集合长度为10
}

int main() {
    for (int i = 69; i < 1000; i++) {
        string s1, s2;
        i2s(i * i, s1);
        i2s(i * i * i, s2);
        if (check(s1 + s2)) { // 字符串相加为相连
            cout << i << endl;
            break;
        }
    }
    return 0;
}

 2.蓝桥杯2015A7手链样式

小明有3颗红珊瑚,4颗白珊瑚,5颗黄玛瑙。
他想用它们串成一圈作为手链,送给女朋友。
现在小明想知道:如果考虑手链可以随意转动或翻转,一共可以有多少不同的组合样式呢?

#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;

int main() {
    string s = "aaabbbbccccc";
    vector<string> v1;
    int ans = 0;
    do {
        // 排除重复,对于v1中的每个元素进行检查,如果存在s的旋转或者翻转,则跳过
        int i;
        for (i = 0; i < v1.size(); i++) {
            if (v1[i].find(s) != string::npos) // string::npos: 不存在 // 找到s的旋转或翻转
                break;
        }
        // s不可用的情况
        if (i != v1.size()) continue;
        string s2 = s + s;
        v1.push_back(s2); // 便于判断旋转的情况
        reverse(s2.begin(), s2.end());
        v1.push_back(s2); // 将s的翻转放入vector中

        ans++;
    }while(next_permutation(s.begin(), s.end())); // 全排列

    cout << ans << endl;

    return 0;
}

 3.蓝桥杯2016A8四平方和

四平方和定理,又称为拉格朗日定理。
每个整数都能表示为至多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<=d,并对所有的可能表示法按a,b,c,d为联合主键升序排列,最后输出第一个表示法。
程序输入为一个整数N(N<5 000 000),要求输出4个非负整数,按从小到大排列, 中间用空格分开。
例如,输入773535,输出 1 1 267 838。

#include <cstdio>
#include <map>
#include <cmath>
using namespace std;

// 将4个数转换为2个数加2个数,判断某个数是否可以表示为2个数的平方和
int N;
map<int, int> cache; // 用map表示缓存
int main() {
    scanf("%d", &N);
    for (int c = 0; c * c <= N / 2; c++) {
        for (int d = c; c * c + d * d <= N; d++) {
            if (cache.find(c * c + d * d) == cache.end()) { // 未找到
                cache[c * c + d * d] = c; // 注意此处将能查出来的平方和作为下标,值为c
            }
        }
    }

    for (int a = 0; a * a <= N / 4; a++) {
        for (int b = a; a * a + b * b <= N / 2; b++) {
            if (cache.find(N - a * a - b * b) != cache.end()) { // 找到
                int c = cache[N - a * a - b * b];
                int d = int(sqrt(N - a * a - b * b - c * c));
                printf("%d %d %d %d", a, b, c, d);

                return 0;
            }
        }
    }
}

 二、dfs 

 1.蓝桥杯2015A6牌型总数

小明被劫持到X赌城,被迫与其他3人玩牌。
一副扑克牌(去掉大小王牌,共52张),均匀发给4个人,每个人13张。
这时,小明脑子里突然冒出一个问题: 
如果不考虑花色,只考虑点数,也不考虑自己得到的牌的先后顺序,
自己手里能拿到的初始牌型组合一共有多少种呢?
请填写该整数,不要填写任何多余的内容或说明文字。

// 一共13张牌,每种牌的个数为0,1,2,3,共多少种组合
#include <iostream>
#include <sstream>
using namespace std;

int ans = 0;

void f(int k, int cnt) { // k代表牌型,cnt代表牌的总数
    if (cnt > 13 || k > 13) return; // 结束点:牌型>13或牌的总数>13
    if (k == 13 && cnt == 13) { // 结束点:牌型=13(到达牌型终点)且牌的总数=13
        ans++;
        return;
    }
    for (int i = 0; i < 5; i++) {
        f(k + 1, cnt + i);
    }
}
void i2s(int num, string &str) {
    stringstream ss;
    ss << num;
    ss >> str;
}

int main() {
    f(0, 0);
    cout << ans << endl;
    return 0;
}

 2.蓝桥杯2017A1迷宫

X星球的一处迷宫游乐场建在某个小山坡上。它是由10x10相互连通的小房间组成的。
房间的地板上写着一个很大的字母。我们假设玩家是面朝上坡的方向站立,则:
L表示走到左边的房间,
R表示走到右边的房间,
U表示走到上坡方向的房间,
D表示走到下坡方向的房间。
开始的时候,直升机把100名玩家放入一个个小房间内。玩家一定要按照地上的字母移动。
迷宫地图如下:
------------
UDDLUULRUL
UURLLLRRRU
RRUURLDLRD
RUDDDDUUUU
URUDLLRRUU
DURLRLDLRL
ULLURLLRDU
RDLULLRDDD
UUDDUDUDLL
ULRDLUURRR
------------
请你计算一下,最后,有多少玩家会走出迷宫? 而不是在里边兜圈子。

#include <iostream>
#include <cstring>
using namespace std;

// dfs+标记(防止绕圈子)
string data1[10]; // 改为data1防止重名
int vis[10][10];
int ans;

bool solve(int i, int j) {
    if (i < 0 || i > 9 || j < 0 || j > 9)
        return true;
    if (vis[i][j] == 1)
        return false;
    vis[i][j] = 1;
    switch(data1[i][j]) {
        case 'U':
            return solve(i - 1, j);
        case 'D':
            return solve(i + 1, j);
        case 'L':
            return solve(i, j - 1);
        case 'R':
            return solve(i, j + 1);
        default:
            return false;
    }
}

int main() {
    data1[0] = "UDDLUULRUL";
    data1[1] = "UURLLLRRRU";
    data1[2] = "RRUURLDLRD";
    data1[3] = "RUDDDDUUUU";
    data1[4] = "URUDLLRRUU";
    data1[5] = "DURLRLDLRL";
    data1[6] = "ULLURLLRDU";
    data1[7] = "RDLULLRDDD";
    data1[8] = "UUDDUDUDLL";
    data1[9] = "ULRDLUURRR";

    for (int i = 0; i < 10; i++) {
        for (int j = 0; j < 10; j++) {
            memset(vis, 0, sizeof(vis));
            bool res = solve(i, j);
            if (res)
                ans++;
        }
    }
    cout << ans << endl;
    return 0;
}

  三、回溯

 1.蓝桥杯2016A3方格填数

如下的10个格子
   +--+--+--+
   |  |  |  |
+--+--+--+--+
|  |  |  |  |
+--+--+--+--+
|  |  |  |
+--+--+--+
填入0~9的数字。
要求:连续的两个数字不能相邻。
(左右、上下、对角都算相邻)
一共有多少种可能的填数方案?
请填写表示方案数目的整数。

#include <iostream>
using namespace std;
int ans = 0;
int a[10] = {0,1, 2, 3, 4, 5, 6, 7, 8, 9};

bool check() {
    if (
            abs(a[0] - a[1]) == 1 ||
            abs(a[0] - a[3]) == 1 ||
            abs(a[0] - a[4]) == 1 ||
            abs(a[0] - a[5]) == 1 ||

            abs(a[1] - a[2]) == 1 ||
            abs(a[1] - a[4]) == 1 ||
            abs(a[1] - a[5]) == 1 ||
            abs(a[1] - a[6]) == 1 ||

            abs(a[2] - a[5]) == 1 ||
            abs(a[2] - a[6]) == 1 ||

            abs(a[3] - a[4]) == 1 ||
            abs(a[3] - a[7]) == 1 ||
            abs(a[3] - a[8]) == 1 ||

            abs(a[4] - a[5]) == 1 ||
            abs(a[4] - a[7]) == 1 ||
            abs(a[4] - a[8]) == 1 ||
            abs(a[4] - a[9]) == 1 ||

            abs(a[5] - a[6]) == 1 ||
            abs(a[5] - a[8]) == 1 ||
            abs(a[5] - a[9]) == 1 ||

            abs(a[6] - a[9]) == 1 ||

            abs(a[7] - a[8]) == 1 ||

            abs(a[8] - a[9]) == 1
    ) {
        return false;
    }
    return true;
}

// 考虑第k个位置,一般从0开始
void f(int k) {
    // 出口
    if (k == 10) {
        bool b = check();
        if (b) {
            ans++;
        }
        return; // 注意这里要return
    }

    for (int i = k; i < 10; i++) {
        // 尝试将位置i与位置k交换,以此确定k位的值
        int t = a[i];
        a[i] = a[k];
        a[k] = t;

        f(k + 1);

        // 回溯
        t = a[i];
        a[i] = a[k];
        a[k] = t;
    }
}

int main() {
    f(0);
    cout << ans << endl;

    return 0;
}

  2.蓝桥杯2016A7剪邮票

如【图1.jpg】, 有12张连在一起的12生肖的邮票。
现在你要从中剪下5张来,要求必须是连着的。
(仅仅连接一个角不算相连) 比如,【图2.jpg】,【图3.jpg】中,粉红色所示部分就是合格的剪取。

// 想法1:每个格子作为起点,dfs连5张,去重:对于T字型失效
// 想法2:枚举所有的5张牌的组合(全排列),检查它们是不是一个连通块(dfs)

#include <iostream>
#include <set>
using namespace std;

int ans = 0;
int a[] = {0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1}; // 它的全排列代表着12选5的一个方案

void dfs(int g[3][4], int i, int j) {
    g[i][j] = 0;
    if (i - 1 >= 0 && g[i - 1][j] == 1) {
        dfs(g, i - 1, j);
    }
    else if (i + 1 <= 2 && g[i + 1][j] == 1) {
        dfs(g, i + 1, j);
    }
    else if (j - 1 >= 0 && g[i][j - 1] == 1) {
        dfs(g, i, j - 1);
    }
    else if (j + 1 <= 3 && g[i][j + 1] == 1) {
        dfs(g, i, j + 1);
    }
}
bool check() {
    int g[3][4];

    // 将某个排列映射到二维矩阵上
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 4; j++) {
            if (a[i * 4 + j]) g[i][j] = 1;
            else g[i][j] = 0;
        }
    }

    int cnt = 0; // 连通块的数目
    // g上面有5个格子被标记为1,现在采用dfs做连通性检查,要求只有一个连通块
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 4; j++) {
            if (g[i][j] == 1) {
                dfs(g, i, j);
                cnt++;
            }
        }
    }
    return cnt = 1; // 判断连通块是否只有一个,否则有多个分区
}

set<string> s1;
void i2s(string &s) {
    for (int i = 0; i < 12; i++) {
        s.insert(s.end(), a[i] + '0');
    }
}

// 由于不同的0排列和1排列是相同的,所以此处需要去重
bool isExist() {
    string a_str;
    i2s(a_str);
    if (s1.find(a_str) == s1.end()) { // 未找到
        s1.insert(a_str);
        return false;
    }
    else {
        return true;
    }
}

void f(int k) {
    if (k == 12) {
        if (!isExist() && check()) {  // 去除重复情况
            ans++;
        }
    }
    for (int i = k; i < 12; i++) {
        {int t = a[i]; a[i] = a[k]; a[k] = t;}
        f(k + 1);
        {int t = a[i]; a[i] = a[k]; a[k] = t;}
    }
}

int main() {
    f(0);
    cout << ans << endl;
    return 0;
}

 四、排序

 1.蓝桥杯2016A4快速排序

排序在各种场合经常被用到。
快速排序是十分常用的高效率的算法。
其思想是:先选一个“标尺”, 用它把整个队列过一遍筛子,以保证:其左边的元素都不大于它,其右边的元素都不小于它。
这样,排序问题就被分割为两个子区间。再分别对子区间排序就可以了。
下面的代码是一种实现,请分析并填写划线部分缺少的代码。

#include <cstdio>

void swap(int a[], int i, int j) {
    int t = a[i];
    a[i] = a[j];
    a[j] = t;
}

int partition(int a[], int p, int r) {
    int i = p; // 该段左端点
    int j = r + 1; // 该段右端点
    int x = a[p]; // 左端点值
    while (true) {
        while (i < r && a[++i] < x); // i向右走,走到r之前,比x小的最右边,即指向第一个>x处
        while (a[--j] > x); // j向左走,走到比x大的最左边,即指向第一个<x处
        if (i >= j) break;
        swap(a, i, j);
    }
    //填空
    swap(a, p, j);
    return j; // j为分界点
}

void quicksort(int a[], int p, int r) {
    if(p<r) {
        int q = partition(a,p,r);
        quicksort(a,p,q-1);
        quicksort(a,q+1,r);
    }
}

int main() {
    int i;
    int a[] = {5,13,6,24,2,8,19,27,6,12,1,17};
    int N = 12;

    quicksort(a, 0, N-1);

    for (i = 0; i < N; i++) printf("%d ", a[i]);
    printf("\n");

    return 0;
}

 五、其他

 1.蓝桥杯2016A6消除尾一

二进制位运算

下面的代码把一个整数的二进制表示的最右边的连续的1全部变成0 如果最后一位是0,则原数字保持不变。
如果采用代码中的测试数据,应该输出: 00000000000000000000000001100111 00000000000000000000000001100000 00000000000000000000000000001100 00000000000000000000000000001100。
请仔细阅读程序,填写划线部分缺少的代码。

#include <cstdio>

void f(int x) {
    int i;
    // 转换前
    for ( i = 0; i < 32; i++) printf("%d", ( x >> (31 - i)) & 1);
    printf(" ");

    // 填空
    x = x & (x + 1);

    // 转换后
    for(i = 0; i < 32; i++) printf("%d", (x >> (31 - i)) & 1);
    printf("\n");
}

int main() {
    f(103);
    f(12);
    return 0;
}
举报

相关推荐

0 条评论