2016年第七届 蓝桥杯B组C/C++ 决赛题解
1.一步之遥
从昏迷中醒来,小明发现自己被关在X星球的废矿车里。
矿车停在平直的废弃的轨道上。
他的面前是两个按钮,分别写着“F”和“B”。
小明突然记起来,这两个按钮可以控制矿车在轨道上前进和后退。
按F,会前进97米。按B会后退127米。
透过昏暗的灯光,小明看到自己前方1米远正好有个监控探头。
他必须设法使得矿车正好停在摄像头的下方,才有机会争取同伴的援助。
或许,通过多次操作F和B可以办到。
矿车上的动力已经不太足,黄色的警示灯在默默闪烁...
每次进行 F 或 B 操作都会消耗一定的能量。
小明飞快地计算,至少要多少次操作,才能把矿车准确地停在前方1米远的地方。
请填写为了达成目标,最少需要操作的次数。
公式 + 暴力算一下即可
void solve() {
for (int i = 0; i < 100; ++i)
for (int j = i + 1; j < 100; ++j)
if (97 * j - 127 * i == 1)
cout << i + j << "\n";
}
2.凑平方数
把0~9这10个数字,分成多个组,每个组恰好是一个平方数,这是能够办到的。
比如:0, 36, 5948721
再比如:
1098524736
1, 25, 6390784
0, 4, 289, 15376
等等...
注意,0可以作为独立的数字,但不能作为多位数字的开始。
分组时,必须用完所有的数字,不能重复,不能遗漏。
如果不计较小组内数据的先后顺序,请问有多少种不同的分组方案?
300 跑DFS,用set去重
using ll = long long;
int a[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
ll n[10];
set<string> s;
void dfs(int step, int num) {
if (step == 10) {
ll nn[10];
for (int i = 0; i < num; ++i) nn[i] = n[i];
sort(nn, nn + num);
string xu;
char str[100];
for (int i = 0; i < num; i++) {
sprintf(str, "%lld", nn[i]); //转换成字符串
xu += str;
xu += "-";
}
// cout << xu << endl;
s.insert(xu); //利用去重的特性插入
return;
}
if (a[step] == 0) { //0也是一个完全平方数
n[num] = 0;
dfs(step + 1, num + 1);
} else {
ll sum = 0;
for (int i = step; i < 10; i++) {
sum = sum * 10 + a[i]; //算出当前所得到的值
double son = sqrt(sum);
if (son == (int)son) { //构成一个完全平方数
n[num] = sum;
dfs(i + 1, num + 1);
}
}
}
}
void solve() {
do {
memset(n, 0, sizeof(n));
dfs(0, 0);
} while (next_permutation(a, a + 10));
cout << s.size();
}
3.棋子换位
有n个棋子A,n个棋子B,在棋盘上排成一行。
它们中间隔着一个空位,用“.”表示,比如:
AAA.BBB
现在需要所有的A棋子和B棋子交换位置。
移动棋子的规则是:
- A棋子只能往右边移动,B棋子只能往左边移动。
- 每个棋子可以移动到相邻的空位。
- 每个棋子可以跳过相异的一个棋子落入空位(A跳过B或者B跳过A)。
AAA.BBB 可以走法:
移动A ==> AA.ABBB
移动B ==> AAAB.BB
跳走的例子:
AA.ABBB ==> AABA.BB
以下的程序完成了AB换位的功能,请仔细阅读分析源码,填写划线部分缺失的内容。
#include <stdio.h>
#include <string.h>
void move(char *data, int from, int to) {
data[to] = data[from];
data[from] = '.';
}
int valid(char *data, int k) {
if (k < 0 || k >= strlen(data)) return 0;
return 1;
}
void f(char *data) {
int i;
int tag;
int dd = 0; // 移动方向
while (1) {
tag = 0;
for (i = 0; i < strlen(data); i++) {
if (data[i] == '.') continue;
if (data[i] == 'A') dd = 1;
if (data[i] == 'B') dd = -1;
if (valid(data, i + dd) && valid(data, i + dd + dd) && data[i + dd] != data[i] && data[i + dd + dd] == '.') {
//如果能跳...
move(data, i, i + dd + dd);
printf("%s\n", data);
tag = 1;
break;
}
}
if (tag) continue;
for (i = 0; i < strlen(data); i++) {
if (data[i] == '.') continue;
if (data[i] == 'A') dd = 1;
if (data[i] == 'B') dd = -1;
if (valid(data, i + dd) && data[i + dd] == '.') {
// 如果能移动...
if (______________________) continue; //填空位置
move(data, i, i + dd);
printf("%s\n", data);
tag = 1;
break;
}
}
if (tag == 0) break;
}
}
int main() {
char data[] = "AAA.BBB";
f(data);
return 0;
}
思路来自这个博客:Here
由所给代码可以分析出移动规则:跳着走的优先级高于移到相邻位置的优先级,在每一步的移动中,如果某一位满足跳走,那么执行跳走后就直接进行下一个状态的判断;假如该状态下不存在跳走的情况,那么就选择走到相邻位置的走法。需要填空代码的位置显然是如果满足这个条件,则不执行走到相邻位置,不满足这个条件时则移动到相邻的位置。解题的关键就是找出这个判断条件,现在通过手动模拟AAA.BBB的移动过程来发现这个规律。
①A②A③A.①B②B③B //初始状态(前面的数字代表字母的编码)(此状态下无跳走,③A可以右移,①B可以左移,但是先判断③A位置) ①A②A.③A①B②B③B //此状态下①B满足跳走 ①A②A①B③A.②B③B //此时无跳走,③A可以右移,②B可以左移
此时也许可以发现,③A的两侧都是B即相同(PS忽略符号’.’),②B两侧1A1B即相异
假如选择③A右移即选择相同的移动,则以后的移动状态为(省略字母编码):AAB.ABB->A.BAABB->AB.AABB->B.AAABB(卡死了)
若选择②B左移即选择相异的移动,以后的移动状态为:
AABAB.B->AAB.BAB->A.BABAB->.ABABAB->BA.ABAB->BABA.AB ->BABABA.->BABAB.A->BAB.BAA->B.BABAA->BB.ABAA->BBB.AAA
即达到所要求目标。
即可猜测所填条件为当可移动位置的左右两侧字母相同时则不选择移动(写代码是要考虑到符号’.’的占位问题)。答案
valid(data, i + dd + dd) && valid(data, i - dd) && data[i + dd + dd] == data[i - dd]
4.机器人塔
X星球的机器人表演拉拉队有两种服装,A和B。
他们这次表演的是搭机器人塔。
类似:
A
B B
A B A
A A B B
B B B A B
A B A B B A
队内的组塔规则是:
A 只能站在 AA 或 BB 的肩上。
B 只能站在 AB 或 BA 的肩上。
你的任务是帮助拉拉队计算一下,在给定A与B的人数时,可以组成多少种花样的塔。
输入一行两个整数 M 和 N,空格分开(0<M,N<500),分别表示A、B的人数,保证人数合理性。
要求输出一个整数,表示可以产生的花样种数。
例如:
用户输入:
1 2
程序应该输出:
3
再例如:
用户输入:
3 3
程序应该输出:
4
二进制枚举
枚举最底下一行的排列状态。最底下一行的排列状态确定后,上面的排列状态也就确定了,所以,只要统计构建的人塔中A和B的数量是否与输入相同,相同的话种类数+1。
什么是二进制枚举?以本题为例,每一个位置要么放A要么放B,这和二进制有点像啊,那就让A对应1,B对应0。
这样一来,1 1 0什么意思?A A B的意思吧。加入最底下一行有3个人,所有的可能,BBB,BBA,BAB,BAA,ABB,ABA,AAB,AAA,对应的二进制为,000,001,010,011,100,101,110,111,这不就是 2^3吗。所以,最后一行的排列状态就可以通过循环表现出来了, for(int i=0;i<=(1<<3)-1;i++)
const int N = 50;
int dp[N]; //用一维数组表示二维状态,有点类似01背包问题优化的思想,但不是01背包!
void solve() {
int m, n, ans = 0;
cin >> m >> n;
int h = int(sqrt(2 * (m + n)));
for (int num = 0; num <= (1 << h) - 1; ++num) {
int tmp_h = h, tmp = num, cnt_a = 0, cnt_b = 0;
for (int i = tmp_h; i >= 1; i--, tmp >>= 1) {
dp[i] = tmp & 1; //初始化最底下一行
if (dp[i]) cnt_a++;
else
cnt_b++;
}
while (tmp_h--) {
for (int i = 1; i <= tmp_h; ++i) {
if (dp[i + 1] == dp[i]) cnt_a++, dp[i] = 1;
else
cnt_b++, dp[i] = 0;
}
}
if (cnt_a == m and cnt_b == n) ans++;
}
cout << ans << "\n";
}
DFS解法
如果:有1个A和2个B,那么有三种方案:
A(0 =1 ^ 1) | ||
---|---|---|
B(1) | ^ | B(1) |
B(1 = 0 ^ 1) | ||
---|---|---|
A(0) | ^ | B(1) |
B(1 = 1 ^ 0) | ||
---|---|---|
B(1) | ^ | A(0) |
那么就可以用0代表A,1代表B;
还要一点要说,题目的意思是从下向上摆(下面多上面少),为了写程序方便(二维数组从一行开始),我们从从上向下摆,显然结果是一样的。
(注意:下面是按反过了的模型说的(倒三角型))
那么只要知道第一行的数据,就可以依次更新下面的数据(反对角线方向从上向下);也就是说只用枚举一行的所有可能就可以了,第2行开始的结果都是根据上一行来的。
如何枚举一行:第一行每一个位置不是1就是0,每一个位置每次从0开始枚举,到1结束;枚举方向从左到右;
例如:拿样例来说吧:1个A,2个B也就是1个0,2个1;
(注意:这里枚举0,1是在第一行,其他行都是根据第一行来更新的)
0 // 第一行第一个位置为0,此时不用更新
0 0 // 第一行第二个位置为0,此时更新第二行
1 // 不合格(约束:1个0)
0 1 // 第一行第二个位置为1,此时更新第二行
1 // 合格,方案数加一
1 // 第一行第一个位置为1,此时不更新
1 0 // 第一行第二个位置为0,此时更新
1 // 合格,方案数加1
1 1 // 第一行第二个位置为1,此时更新
0 // 合格,方案数加1
此时已经枚举完毕,总方案数为3。
(注意:一直都是枚举的第一行的每一种方案,其他行都是根据第一行更新得到的)
如何统计A,B的个数(也就0,1的个数):加入用cnt计数,每次枚举的时候直接cnt+=枚举的那个位置上的数,如果是1那就加上了,是0就相当于没加,那这不就是统计了1的个数吗!! 0的个数用当前规模的总个数减去1的个数就可以了。
注意事项:
题目中提到:测试数据都合法。也就是说可以算出一行都多少个数字;
* * * *
* * *
* *
*
上面的这个图,就是每次更新的范围,所有*的总个数就是A,B的总和;
在知道了A,B的个数之和,就可以算出第一行有几个数(人);
怎么算?? ::: 等差数列求和,公差为1;前n项和就是 (1 + n)* n / 2
假设第一行只有一个数(x=1),计算以边前n项和看是否相等,如果相等那么第一行就有x个数,如果不x+1
,然后看是否相等,最后就得到了第一行能放几个数(x)。
注意:题目的要点就是枚举第一行有多少种可能,每种情况用来去更新其他行。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 510;
int m; // A的个数(即0的个数)
int n; // B的个数(即1的个数)
int p[N][N]; // c存放当前的摆放方案
int cnt; // 当前B的个数(即1的个数)
int x = 1; // 第一行最多有多少个人
ll ans; // 总的方案数
void dfs(int cur) {
// 0和1的个数大于给定的个数,直接返回上一层
if (cnt > n || cur * (cur + 1) / 2 - cnt > m) return;
if (cur == x) {
ans++;
return;
}
for (int i = 0; i < 2; ++i) {
p[0][cur] = i;
cnt += i; // 加号的数量
for (int j = 1; j <= cur; ++j) { // 更新受影响的位置
p[j][cur - j] = p[j - 1][cur - j] ^ p[j - 1][cur - j + 1];
cnt += p[j][cur - j];
}
dfs(cur + 1); // 去下一个位置
for (int j = 1; j <= cur; ++j) cnt -= p[j][cur - j]; // 回溯
cnt -= i; // 回溯
}
}
int main() {
scanf("%d%d", &m, &n);
while (x * (x + 1) / 2 != m + n) x++; // 第一行有多少个数字(题目中的人)
dfs(0);
printf("%lld\n", ans);
return 0;
}
5.广场舞
LQ市的市民广场是一个多边形,广场上铺满了大理石的地板砖。
地板砖铺得方方正正,就像坐标轴纸一样。
以某四块砖相接的点为原点,地板砖的两条边为两个正方向,一块砖的边长为横纵坐标的单位长度,则所有横纵坐标都为整数的点都是四块砖的交点(如果在广场内)。
广场的砖单调无趣,却给跳广场舞的市民们提供了绝佳的参照物。每天傍晚,都会有大批市民前来跳舞。
舞者每次都会选一块完整的砖来跳舞,两个人不会选择同一块砖,如果一块砖在广场边上导致缺角或者边不完整,则没人会选这块砖。
(广场形状的例子参考【图1.png】)
现在,告诉你广场的形状,请帮LQ市的市长计算一下,同一时刻最多有多少市民可以在广场跳舞。
【输入格式】
输入的第一行包含一个整数n,表示广场是n边形的(因此有n个顶点)。
接下来n行,每行两个整数,依次表示n边形每个顶点的坐标(也就是说广场边缘拐弯的地方都在砖的顶角上。数据保证广场是一个简单多边形。
【输出格式】
输出一个整数,表示最多有多少市民可以在广场跳舞。
【样例输入】
5
3 3
6 4
4 1
1 -1
0 4
【样例输出】
7
【样例说明】
广场如图1.png所示,一共有7块完整的地板砖,因此最多能有7位市民一起跳舞。
【数据规模与约定】
对于30%的数据,n不超过100,横纵坐标的绝对值均不超过100。
对于50%的数据,n不超过1000,横纵坐标的绝对值均不超过1000。
对于100%的数据,n不超过1000,横纵坐标的绝对值均不超过100000000(一亿)。
解题思路:
1、先找到所有的点 最大的横纵点和最小的横纵点,然后判断范围内的其他三个点是否在这个多边形中
2、每判断一次如果符合条件就计数加一,如不符合就重新遍历
#include <iostream>
using namespace std;
int sum_1 = 0, n;
int max(int a, int b) {
if (a > b)
return a;
else
return b;
}
int min(int a, int b) {
if (a > b)
return b;
else
return a;
}
bool demo_point(int point[][2], int x, int y) {
bool flag = false;
int i = 0, j = n - 1;
for (; i < n; i++) //遍历每一个坐标
{
if (min(point[i][1], point[j][1]) < y && max(point[i][1], point[j][1]) >= y) //得到纵坐标最小值并且纵坐标最大值
{
double t = (double)point[i][0] + (double)(((double)(y - point[i][1]) / (double)(point[i][1] - point[j][1])) * ((double)(point[i][0] - point[j][0])));
//求斜率
if (t < x) {
flag = !flag;
}
}
j = i;
}
return flag;
}
int main() {
int i;
int point[100][2];
int max_x = -100000, max_y = -100000;
int min_x = 100000, min_y = 100000;
cin >> n;
for (i = 0; i < n; i++) {
cin >> point[i][0] >> point[i][1];
if (point[i][0] > max_x) max_x = point[i][0];
if (point[i][0] < min_x) min_x = point[i][0];
if (point[i][1] > max_y) max_y = point[i][1];
if (point[i][1] < min_y) min_y = point[i][1];
}
for (i = min_x; i < max_x; i++) { //x从最小到最大
for (int j = min_y; j < max_y; j++) { //y从最小到最大
if (demo_point(point, i, j) && demo_point(point, i + 1, j) && demo_point(point, i, j + 1) && demo_point(point, i + 1, j + 1)) { //其他三个点是否构成一个方格且满足范围
sum_1++;
}
}
}
cout << sum_1 << endl;
return 0;
}
6.生成树计数
给定一个 nm 的格点图,包含 n 行 m 列共 nm 个顶点,相邻的顶点之间有一条边。
【图1.png】给出了一个3*4的格点图的例子。
如果在图中删除部分顶点和其相邻的边,如上图删除第2行第3列和第3行第1列的顶点后,如【图2.png】所示。
图的生成树指包含图中的所有顶点和其中的一部分边,使得任意两个顶点之间都有由边构成的唯一路径。如果两个生成树包含有不同的边即被认为不同,则上图中共有31种不同的生成树,其中a边不选有10种,a边选有21种。
给出格点图中保留的顶点的信息,请计算该图一共有多少种不同的生成树。
【输入格式】
输入的第一行包含两个整数n, m,用空格分隔,表示格点图的行数和列数。
接下来n行,每行m个字母(中间没有分隔字符),每个字母必然是大写E或大写N,E表示对应的顶点存在,N表示对应的顶点不存在。保证存在至少一个顶点。
【输出格式】
输出一行,包含一个整数,表示生成树的个数。答案可能很大,你只需要计算答案除以1000000007的余数即可。
【样例输入】
3 4
EEEE
EENE
NEEE
【样例输出】
31
【数据规模与约定】
对于10%的数据,1<=n<=2。
对于30%的数据,1<=n<=3。
对于40%的数据,1<=n<=4。
对于50%的数据,1<=n<=5。
另有20%的数据,1<=n*m<=12。
另有10%的数据,1<=m<=15。
对于100%的数据,1<=n<=6,1<=m<=100000。
资源约定:
峰值内存消耗 < 256M
CPU消耗 < 4500ms