目录

一、暴力枚举例题

1.蓝桥杯2013A7错误票据

2.蓝桥杯2013A4颠倒的价牌

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

4.蓝桥杯2015A7手链样式

5.蓝桥杯2016A8四平方和

二、字符串

1.蓝桥杯2013A5前缀判断

三、递归

1.蓝桥杯2013A6逆波兰表达式

2. 蓝桥杯2014A8地宫取宝

3. 蓝桥杯2017A5字母组串

4.蓝桥杯2017A7正则问题

四、dfs 

1.蓝桥杯2015A6牌型总数

2.蓝桥杯2016A1迷宫

五、回溯

1.蓝桥杯2016A3方格填数

2.蓝桥杯2016A7剪邮票

3.蓝桥杯2017A4方格分割

六、动态规划

1.蓝桥杯2017A6最大公共子串

七、排序

1.蓝桥杯2016A4快速排序

八、其他

1.蓝桥杯2016A6消除尾一


一、暴力枚举例题

1.蓝桥杯2013A7错误票据

某涉密单位下发了某种票据,并要在年终全部收回。
每张票据有唯一的ID号。全年所有票据的ID号是连续的,但ID的开始数码是随机选定的。
因为工作人员疏忽,在录入ID号的时候发生了一处错误,造成了某个ID断号,另外一个ID重号。
你的任务是通过编程,找出断号的ID和重号的ID。
假设断号不可能发生在最大和最小号。

要求程序首先输入一个整数N(N<100)表示后面数据行数。
接着读入N行数据。
每行数据长度不等,是用空格分开的若干个(不大于100个)正整数(不大于100000)
每个整数代表一个ID号。

要求程序输出1行,含两个整数m n,用空格分隔。
其中,m表示断号ID,n表示重号ID

例如:
用户输入:
2
5 6 8 11 9 
10 12 9
则程序输出:
7 9

再例如:
用户输入:
6
164 178 108 109 180 155 141 159 104 182 179 118 137 184 115 124 125 129 168 196
172 189 127 107 112 192 103 131 133 169 158 
128 102 110 148 139 157 140 195 197
185 152 135 106 123 173 122 136 174 191 145 116 151 143 175 120 161 134 162 190
149 138 142 146 199 126 165 156 153 193 144 166 170 121 171 132 101 194 187 188
113 130 176 154 177 120 117 150 114 183 186 181 100 163 160 167 147 198 111 119
则程序输出:
105 120

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

#define MaxN 10001

int line;
int data1[MaxN];

void stoi (string &str, int &num) {
    stringstream ss;
    ss << str;
    ss >> num;
}

int main () {
    scanf("%d", &line);
    getchar();

    int a = 0;
    for (int i = 0; i < line; i++) {
        string s;
        getline(cin, s);
        istringstream iss(s);
        string tmp;
        while (getline(iss, tmp, ' ')) {
            stoi(tmp, data1[a++]);
        }
    }
    //cout << a << endl;

    // 排序
    sort(data1, data1 + a);

    int m, n;
    for (int i = 1; i < a; i++) {
        if (data1[i] == data1[i - 1] + 2) m = data1[i] - 1;
        if (data1[i] == data1[i - 1]) n = data1[i];
    }
    cout << m << " " << n << endl;
    return 0;
}

2.蓝桥杯2013A4颠倒的价牌

小李的店里专卖其它店中下架的样品电视机,可称为:样品电视专卖店。
其标价都是4位数字(即千元不等)。
小李为了标价清晰、方便,使用了预制的类似数码管的标价签,只要用颜色笔涂数字就可以了(参见p1.jpg)。
这种价牌有个特点,对一些数字,倒过来看也是合理的数字。如:1 2 5 6 8 9 0 都可以。这样一来,如果牌子挂倒了,有可能完全变成了另一个价格,比如:1958 倒着挂就是:8561,差了几千元啊!! 
当然,多数情况不能倒读,比如,1110 就不能倒过来,因为0不能作为开始数字。
有一天,悲剧终于发生了。某个店员不小心把店里的某两个价格牌给挂倒了。并且这两个价格牌的电视机都卖出去了!
庆幸的是价格出入不大,其中一个价牌赔了2百多,另一个价牌却赚了8百多,综合起来,反而多赚了558元。
请根据这些信息计算:赔钱的那个价牌正确的价格应该是多少?
答案是一个4位的整数,请通过浏览器直接提交该数字。

void itos(int &num, string &str) {
    stringstream ss;
    ss << num;
    ss >> str;
}

void stoi(string &str, int &num) {
    stringstream ss;
    ss << str;
    ss >> num;
}

char to(char x) {
    if (x == '6') return '9';
    else if (x == '9') return '6';
    else return x;
}

string reverse(string &str) {
    string ans;

    for (int i = 3; i >= 0; i--) {
            ans.insert(ans.end(), to(str[i]));
    }
    return ans;
}

struct price {
    int a, b, c; // 原始价格,翻转后价格,差
};

vector<price> v1; // 存储-200多的
vector<price> v2; // 存储+800多的

int main() {
    for (int i = 1000; i < 10000; i++) {
        string str;
        itos(i, str);

        if (str.find('3') != string::npos || str.find('4') != string::npos || str.find('7') != string::npos || str.rfind('0') == 3) { // 字符串中无347,最后一位不是0
            continue;
        }

        string r = reverse(str);
        int r_int;
        stoi(r, r_int); // r_int是翻转后的价格,i是原始价格
        
        int plus = r_int - i;
        if (plus > -300 && plus < -200) {
            price p = {i, r_int, plus};
            v1.push_back(p);
        }
        else if (plus > 800 && plus < 900) {
            price p = {i, r_int, plus};
            v2.push_back(p);
        }

        for (int i = 0; i < v1.size(); i++) {
            for (int j = 0; j < v2.size(); j++) {
                if (v1[i].c + v2[j].c  == 558) {
                    cout << v1[i].a << " " << v1[i].b << " " << v1[i].c << " ";
                    cout << v2[j].a << " " << v2[j].b << " " << v2[j].c << endl;
                }
            }
        }
    }

    return 0;
}

3.蓝桥杯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;
}

4.蓝桥杯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;
}

5.蓝桥杯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;
            }
        }
    }
}

二、字符串

1.蓝桥杯2013A5前缀判断

如下的代码判断 needle_start指向的串是否为haystack_start指向的串的前缀。
如不是,则返回NULL。
比如:"abcd1234" 就包含了 "abc" 为前缀。

#include <iostream>
using namespace std;

char *prefix (char* haystack_start, char* needle_start) {
    char* haystack = haystack_start;
    char* needle = needle_start; // 前缀

    while (*haystack && *needle) { // 两指针都没有越界
        // 填空
        if (*(haystack++) != *(needle++)) return NULL;
    }

    if (*needle) return NULL;

    return haystack_start;
}

int main () {
    cout << prefix((char *)"abc123", (char *)"abc") << endl;

    return 0;
}

三、递推和递归

1.蓝桥杯2013A6逆波兰表达式

正常的表达式称为中缀表达式,运算符在中间,主要是给人阅读的,机器求解并不方便。
例如:3 + 5 * (2 + 6) - 1
而且,常常需要用括号来改变运算次序。
相反,如果使用逆波兰表达式(前缀表达式)表示,上面的算式则表示为:
- + 3 * 5 + 2 6 1
不再需要括号,机器可以用递归的方法很方便地求解。
为了简便,我们假设:
1. 只有 + - * 三种运算符
2. 每个运算数都是一个小于10的非负整数
下面的程序对一个逆波兰表示串进行求值。
其返回值为一个数组:其中第一元素表示求值结果,第二个元素表示它已解析的字符数。

#include <iostream>
using namespace std;

struct EV {
    int result; // 计算结果
    int n; // 已处理字符数
};

struct EV evaluate (char* x) {
    struct EV ev = {0, 0}; // 保存最终计算结果
    struct EV v1;
    struct EV v2;

    if (*x == 0) return ev;

    if (x[0] >= '0' && x[0] <= '9') { // 遇到数字
        ev.result = x[0] - '0'; // 字符转数字
        ev.n = 1;
        return ev;
    }

    v1 = evaluate(x + 1);
    //cout << "v1:" << v1.result << " " << v1.n << endl;
    // 填空
    v2 = evaluate(x + 1 + v1.n);
    //cout << "v2:" << v2.result << " " << v2.n << endl;

    if (x[0] == '+') ev.result = v1.result + v2.result;
    if (x[0] == '-') ev.result = v1.result - v2.result;
    if (x[0] == '*') ev.result = v1.result * v2.result;
    ev.n = 1 + v1.n + v2.n;

    return ev;

}

int main () {
    // 3+5*(2+6)-1
    const EV &ev = evaluate((char*)"-+3*5+261");
    cout << ev.result << " " << ev.n << endl;
    return 0;
}

2. 蓝桥杯2014A8地宫取宝

X 国王有一个地宫宝库。是 n x m 个格子的矩阵。每个格子放一件宝贝。每个宝贝贴着价值标签。
地宫的入口在左上角,出口在右下角。
小明被带到地宫的入口,国王要求他只能向右或向下行走。
走过某个格子时,如果那个格子中的宝贝价值比小明手中任意宝贝价值都大,小明就可以拿起它(当然,也可以不拿)。
当小明走到出口时,如果他手中的宝贝恰好是k件,则这些宝贝就可以送给小明。
请你帮小明算一算,在给定的局面下,他有多少种不同的行动方案能获得这k件宝贝。
输入格式
输入一行3个整数,用空格分开:n m k (1<=n,m<=50, 1<=k<=12)
接下来有 n 行数据,每行有 m 个整数 Ci (0<=Ci<=12)代表这个格子上的宝物的价值
输出格式
要求输出一个整数,表示正好取k个宝贝的行动方案数。该数字可能很大,输出它对 1000000007 取模的结果。
样例输入
2 2 2
1 2
2 1
样例输出
2
样例输入
2 3 2
1 2 3
2 1 5
样例输出
14

// 记忆性递归
// 普通的递归计算f(5)时需要计算f(4)+f(3),计算f(4)时还需要计算f(3)+f(2)但是计算f(3)的时候还要计算f(2)+f(1),这样重复计算f(2),导致时间花费较多,而这里我们可以用一个数组来储存一下每一个已经完成计算的斐波那契数,以用于之后需要时调用。
#include <iostream>
#include <cstring>
using namespace std;

int n, m, k;
int data1[50][50];
long long ans;
long long cache[50][50][14][13];

#define MOD 1000000007

long long dfs(int x, int y, int max, int cnt) {
    // 查缓存
    if (cache[x][y][max + 1][cnt] != -1) {
        return cache[x][y][max + 1][cnt];
    }
    ans = 0;
    int cur = data1[x][y];
    if (x == n || y == m || cnt > k) {
        return 0;
    }
    // 已经面临最后一个格子
    if (x == n - 1 && y == m - 1) {
        if (cnt == k || (cnt == k - 1 && cur > max)) {
            ans++;
            if (ans > MOD) ans %= MOD;
        }
    }

    // 可以取这个物品
    if (cur > max) {
        ans += dfs(x, y + 1, cur, cnt + 1); // 向下
        ans += dfs(x + 1, y, cur, cnt + 1); // 向右
    }

    // 价值较小或价值大但不取这个物品
    ans += dfs(x, y + 1, max, cnt);
    ans += dfs(x + 1, y, max, cnt);

    // 写缓存
    cache[x][y][max + 1][cnt] = ans % MOD;
    return cache[x][y][max + 1][cnt];
}


int main() {
    scanf("%d %d %d", &m, &n, &k);
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            scanf("%d", &data1[i][j]);
        }
    }
    memset(cache, -1, sizeof(cache));
    printf("%lld", dfs(0, 0, -1, 0));

    return 0;
}

3. 蓝桥杯2017A5字母组串

由 A,B,C 这3个字母就可以组成许多串。
比如:”A”,”AB”,”ABC”,”ABA”,”AACBB” ….
现在,小明正在思考一个问题: 
如果每个字母的个数有限定,能组成多少个已知长度的串呢?
他请好朋友来帮忙,很快得到了代码,解决方案超级简单,然而最重要的部分却语焉不详。
请仔细分析源码,填写划线部分缺少的内容。

#include <stdio.h>

// a个A,b个B,c个C 字母,能组成多少个不同的长度为n的串。
int f(int a, int b, int c, int n)
{
    if(a<0 || b<0 || c<0) return 0;
    if(n==0) return 1;
    // 填空
    return f(a - 1, b, c, n - 1) + f(a, b - 1, c ,n - 1) + f(a, b, c - 1, n - 1);
}

int main()
{
    printf("%d\n", f(1,1,1,2));
    printf("%d\n", f(1,2,3,3));
    return 0;
}

4.蓝桥杯2017A7正则问题

 考虑一种简单的正则表达式:
只由 x ( ) | 组成的正则表达式。
小明想求出这个正则表达式能接受的最长字符串的长度。
例如 ((xx|xxx)x|(x|xx))xx 能接受的最长字符串是: xxxxxx,长度是6。
输入格式
一个由x()|组成的正则表达式。
输出格式
输出所给正则表达式能接受的最长字符串的长度。
数据范围
输入长度不超过100,保证合法。
输入样例
((xx|xxx)x|(x|xx))xx
输出样例
6

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

char s[100];
int len;
int pos;
int ans = 0;

// 求出当前字符串,自当前下标到结束能匹配的字符串的长度
int f() {
    int tmp = 0; // 用于保存连续的x的数量
    int m = 0; // 当前x个数的最大值

    while(pos < len) {
        if (s[pos] == 'x') {
            pos++;
            tmp++;
        }
        else if (s[pos] == '|') {
            pos++;
            m = max(m, tmp);
            tmp = 0;
        }
        else if (s[pos] == '(') {
            pos++;
            tmp += f(); // 等待后面的结果并累加到ans
        }
        else if (s[pos] == ')') {
            pos++;
            m = max(m, tmp);
            return m;
        }
    }
    m = max(m, tmp); // 将最后一个|后的x也考虑进去
    return m;
}

int main() {
    scanf("%s", &s);
    len = strlen(s);
    int ans = f();
    printf("%d\n", ans);
    return 0;

5.蓝桥杯2017A8包子凑数

小明几乎每天早晨都会在一家包子铺吃早餐。他发现这家包子铺有N种蒸笼,其中第i种蒸笼恰好能放Ai个包子。每种蒸笼都有非常多笼,可以认为是无限笼。
每当有顾客想买X个包子,卖包子的大叔就会迅速选出若干笼包子来,使得这若干笼中恰好一共有X个包子。比如一共有3种蒸笼,分别能放3、4和5个包子。
当顾客想买11个包子时,大叔就会选2笼3个的再加1笼5个的(也可能选出1笼3个的再加2笼4个的)。
当然有时包子大叔无论如何也凑不出顾客想买的数量。比如一共有3种蒸笼,分别能放4、5和6个包子。而顾客想买7个包子时,大叔就凑不出来了。
小明想知道一共有多少种数目是包子大叔凑不出来的。
输入
第一行包含一个整数N。(1 <= N <= 100)
以下N行每行包含一个整数Ai。(1 <= Ai <= 100)
输出
一个整数代表答案。如果凑不出的数目有无限多个,输出INF。
例如,
输入:
2
4
5
程序应该输出:
6
再例如,
输入:
2
4
6
程序应该输出:
INF
样例解释:
对于样例1,凑不出的数目包括:1, 2, 3, 6, 7, 11。
对于样例2,所有奇数都凑不出来,所以有无限多个。

#include <iostream>
using namespace std;

int n, g;
int a[101];
bool f[10000];

// 求最大公约数
int gcd(int a, int b) {
    if (b == 0) return a;
    return gcd(b, a % b);
}

int main() {
    scanf("%d", &n);
    f[0] = true; // 初始化

    for (int i = 1; i <= n; i++) {
        scanf("%d", &a[i]);
        if (i == 1) {
            g = a[i]; // 初始化最大公约数
        }
        else {
            g = gcd(a[i], g);
        }
        for (int j = 0; j < 10000; j++) {
            if (f[j]) f[j + a[i]] = true; // 递推
        }
    }
    // 所有数不互质时,结果为无穷大
    if (g != 1) {
        printf("INF\n");
        return 0;
    }

    // 统计个数
    int ans = 0;
    for (int i = 0; i < 10000; i++) {
        if (!f[i]) {
            ans++;
            cout << i << endl;
        }
    }
    printf("%d\n", ans);
    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.蓝桥杯2016A1迷宫

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;
}

3.蓝桥杯2017A4方格分割

6x6的方格,沿着格子的边线剪开成两部分。要求这两部分的形状完全相同。
如图:就是可行的分割法。
试计算:包括这3种分法在内,一共有多少种不同的分割方法。注意:旋转对称的属于同一种分割法。
请提交该整数,不要填写任何多余的内容或说明文字。

#include <iostream>
using namespace std;

int dire[4][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
int vis[7][7];
int ans = 0;

void dfs(int x, int y) {
    // 终止条件
    if (x == 0 || y == 0 || x == 6 || y == 6) {
        ans++;
        return;
    }

    vis[x][y] = 1; // 标记
    vis[6 - x][6 - y] = 1; // 对称标记

    // 4个方向
    for (int i = 0; i < 4; i++) {
        int nx = x + dire[i][0];
        int ny = y + dire[i][1];
        // 新坐标
        if (nx < 0 || nx > 6 || ny < 0 || ny > 6) continue;
        if (!vis[nx][ny]) {
            dfs(nx, ny);
        }
    }
    // 回溯
    vis[x][y] = 0;
    vis[6 - x][6 - y] = 0;
}

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

六、动态规划

1.蓝桥杯2017A6最大公共子串

最大公共子串长度问题就是:
求两个串的所有子串中能够匹配上的最大长度是多少。
比如:"abcdkkk" 和 "baabcdadabc",
可以找到的最长的公共子串是"abcd",所以最大公共子串长度为4。
下面的程序是采用矩阵法进行求解的,这对串的规模不大的情况还是比较有效的解法。
请分析该解法的思路,并补全划线部分缺失的代码。

#include <stdio.h>
#include <string.h>

#define N 256
int f(const char* s1, const char* s2) {
    int a[N][N];
    int len1 = strlen(s1);
    int len2 = strlen(s2);
    int i, j;

    memset(a, 0, sizeof(int)*N*N);
    int max = 0;
    for(i = 1; i <= len1; i++) {
        for(j = 1; j <= len2; j++) {
            if(s1[i - 1] == s2[j - 1]) {
                //填空
                a[i][j] = a[i - 1][j - 1] + 1;
                if(a[i][j] > max) max = a[i][j];
            }
        }
    }
    return max;
}

int main() {
    printf("%d\n", f("abefecd", "becd"));
    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;
}
Logo

瓜分20万奖金 获得内推名额 丰厚实物奖励 易参与易上手

更多推荐