2017年第八届“蓝桥杯”国赛B组C/C++ 个人题解
2017-05-31 21:41
197 查看
前言:
我参加了今年第八届的蓝桥杯国赛,只拿了个优秀奖,伤心。官方也没有公布试题和答案,在网上搜索了很久都没有找到蓝桥杯国赛的题目。突然有了一个不自量力的想法,趁还有一点记忆,把题目记录下来,并且附上自己的做法。
【注】以下题目仅靠个人记忆来记录的,由于比赛已经过去好几天,难免会有错漏。如果看到此文的你发现有任何地方表述错误,恳请指出,本人尽快更正。另外,给出的代码和答案也是个人想法而已,希望各位大神能够指出笔者的错误地方,谢谢。
第一题:36进制
题意:
用类似16进制的表示办法,A表示10,B表示11,……,Y表示25,Z表示26,再加上0到9,就可以表示为36进制。那么请问MANY对应的十进制数是多少?
代码:
参考答案:1040254
第二题:瓷砖样式
题意:
有2种不同颜色规格为1*2的瓷砖,用其来铺设地板,不能重叠和越界。并且,地板中任意2*2的格子不能为同一种颜色。如图,当地板为2*3时,有10种铺设方案。问:当地板为3*10时,问有多少种铺设方案?
代码:
参考答案:105760
第三题:希尔伯特曲线
题意:
有一个正方形,可以在上面作出一条希尔伯特曲线。左下角坐标为(1,1),右下角坐标为(2^n, 2^n)。该曲线在2^(n+1)*2^(n+1)上的正方形上的图案,可以由2^n
* 2^n的情况构造出来。构造方法:左上角和右上角保持不变,左下角顺时针90度,右下角逆时针90度,然后各部分按照虚线相连即可。有n<30,x,y<2^30。输入n和p点坐标(x,
y),答案输出曲线上该点覆盖的格子的序号。注:格子的序号是从(1,1)开始沿着曲线所覆盖的第几个格子。
代码:
参考答案:m-y+1
【解释】给出的代码是使用分治法,通过分析给出的4个if语句可以判断出依次是处理左下、右下、左上、右上,需要我们填空的部分是处理右下角的。分治法是将大问题变为相同性质的小问题,因此是要将右下角的图形逆时针90度。
假设正方形是m*m,那么顺时针90度,就是将第1行变为第1列,第2行变为第2列,……第m行变为第m列。而逆时针90度,就是将第1行变为第m列,第2行变为第m-1列,……,第m行变为第1列。右下角的部分需要纵坐标平移m个单位后,再逆时针90度。
顺时针90度:(x, y) ==> (y, x)
逆时针90度:(x, y) ==> (m-y+1, m-x+1)
第四题:找环
题意:
编号为1到n的n个点,以及n-1条边构成一棵树。现在在树上加上一条边,这样就构成了一个含环的图了。请你找出该环上的结点,从小到大输出这些结点编号。
测试数据:
30%数据:n<1000
100%数据:n<100000
输入样例:
5
1 2
2 5
4 2
1 3
5 3
输出样例:
1 2 3 5
【题解】
计算出每个点的度数。明显度数为1的点不可能在环上,那么与该点唯一相连的边也就不在环上了,可以删去。删去这条边后,这边的另一个端点度数减去1,如果因此而导致它的度数变为1了,同样说明该点不在环上,可以去掉,不断重复以上操作,直到没有边可以删去。
参考代码中用一个队列来维护边。由于点数n很大,无法使用邻接矩阵,只能使用邻接表来存储图,代码中使用vector容器来实现。
参考代码:
第五题:在线匹配
题意:
有n个人在线上玩游戏,每人的积分分别为Ai。如果线上的某两个人的积分恰好相差为k时,他们就会被进行匹配。问线上最多会有多少人,他们任意两人均没法进行匹配?
测试数据:
30%数据:n<10
100%数据:n<100000,Ai<100000,k<100000
输入样例1:
10 0
1 4 2 3 6 7 1 4 2 3
输出样例1:
6
输入样例2:
10 3
1 1 1 1 4 4 1 1 1 1
输出样例2:
8
【题解】
方法一:
30%数据n<10,所以可以用深度搜索,每个数只有选和不选,得到一组待定的数列后,再检查一下是否两两不能匹配,是则用当前数列的个数去尝试更新答案。时间复杂度是O(n*2^n)。
方法二:
将每个数值看作点,把可以匹配的两个数连一条边,那么问题转化为:删去最少的点,使得剩下的点中不存在相连的边。
对于每条边,明显两个端点只能选一个,按照贪心思想,易知应该优先去掉度数多的那个点。
算法步骤:
(1)构图,将积分恰好相差k的两个点相连;
(2)计算出所有点的度数;
(3)找出度数最大的那个点;
(4)把与该点相连的所有点的度数减去1,把该点的度数置为-1表示已经删去该点;
(5)重复(4)(5),直到没有边可以删去;
(6)这时剩余的点的度数均为0,统计出其个数,就是最大不能两两匹配的个数。
时间复杂度为O(n^2)
(n<100000,该方法仍然无法通过所有测试数据,希望有大神能够提供更优秀的方法)
参考代码:
第六题:城市旅游
题意:
有n个城市,火车从一个城市到达另一个与其直接相连的城市,花费时间为1。当处于某个城市时,火车会等概率地随机驶向一个与之相连的城市。火车从任意一个城市开始出发。路线保证没有重边和自环,任意一座城市至少有一个可以到达的城市。请你求出达到每个城市的期望时间。
输入描述
第一行是城市数目n和路线数目m
接下来有m行,每行两个正整数u和v,表示城市u可以到达城市v。
输出描述
答案共n行,每行一个数,分别表示火车达到每个城市的期望时间。
(输入输出样例忘了,仅记得输出样例保留了9位小数)
30%数据:n<10
50%数据:n<15
90%数据:n<19
100%数据:n<=21
【题解】
设Pi为城市i可以到达的城市的数量,Ti为到达城市i的期望时间,则可得到式子
,即,由于任意一个城市至少有一个可达的城市,所以Pi>0。n个城市有n条这样的方程,题目要求Ti。不妨将Ti看作是n个未知数,而1/Pi看作是系数,把数值1放在等号右边,于是就得到n条n元一次方程。于是就可以利用线性代数的知识,使用高斯消元法来解矩阵,求出这n个未知数也就是所有Ti的值了。
(其实这题我也不太确定,因为按照我这个方法的话,出题人大可把n的范围取得大一些。测试样例忘了,我也无法验证自己的代码是否正确。希望大神可以多多指教。)
参考代码:
我参加了今年第八届的蓝桥杯国赛,只拿了个优秀奖,伤心。官方也没有公布试题和答案,在网上搜索了很久都没有找到蓝桥杯国赛的题目。突然有了一个不自量力的想法,趁还有一点记忆,把题目记录下来,并且附上自己的做法。
【注】以下题目仅靠个人记忆来记录的,由于比赛已经过去好几天,难免会有错漏。如果看到此文的你发现有任何地方表述错误,恳请指出,本人尽快更正。另外,给出的代码和答案也是个人想法而已,希望各位大神能够指出笔者的错误地方,谢谢。
第一题:36进制
题意:
用类似16进制的表示办法,A表示10,B表示11,……,Y表示25,Z表示26,再加上0到9,就可以表示为36进制。那么请问MANY对应的十进制数是多少?
代码:
#include<stdio.h> int main() { char ch[5] = "MANY"; int ans = 0; for(int i = 0; i < 4; i++) ans = ans * 36 + (ch[i]-'A'+10); printf("%d\n", ans); return 0; }
参考答案:1040254
第二题:瓷砖样式
题意:
有2种不同颜色规格为1*2的瓷砖,用其来铺设地板,不能重叠和越界。并且,地板中任意2*2的格子不能为同一种颜色。如图,当地板为2*3时,有10种铺设方案。问:当地板为3*10时,问有多少种铺设方案?
代码:
#include<stdio.h> #include<string.h> #define maxn 3 #define maxm 10 int a[maxn][maxm]; int ans; bool check() { for(int i = 0; i < maxn-1; i++) for(int j = 0; j < maxm-1; j++) { int p = a[i][j]; if(p == -1) return false; //地板必须全部铺满 if(p == a[i+1][j] && p == a[i][j+1] && p == a[i+1][j+1]) return false; //任意2*2格子不能为同一种颜色 } return true; } void dfs(int cur) //准备铺地板第cur格 { if(cur == maxn * maxm) { if(check()) { ans++; /* for(int i = 0; i < maxn; i++) { for(int j = 0; j < maxm; j++) printf("%d", a[i][j]); printf("\n"); } printf("-------\n"); */ } return; } int x = (cur-1) / maxm; int y = cur - x * maxm -1; //格子(x,y)已经铺有瓷砖 if(a[x][y] != -1) dfs(cur+1); //横着铺 if(y+1 < maxm && a[x][y] == -1 && a[x][y+1] == -1) { a[x][y] = a[x][y+1] = 0; dfs(cur+1); a[x][y] = a[x][y+1] = 1; dfs(cur+1); a[x][y] = a[x][y+1] = -1; } //竖着铺 if(x+1 < maxn && a[x][y] == -1 && a[x+1][y] == -1) { a[x][y] = a[x+1][y] = 0; dfs(cur+1); a[x][y] = a[x+1][y] = 1; dfs(cur+1); a[x][y] = a[x+1][y] = -1; } } int main() { memset(a, -1, sizeof(a)); ans = 0; dfs(1); printf("%d\n", ans); return 0; }
参考答案:105760
第三题:希尔伯特曲线
题意:
有一个正方形,可以在上面作出一条希尔伯特曲线。左下角坐标为(1,1),右下角坐标为(2^n, 2^n)。该曲线在2^(n+1)*2^(n+1)上的正方形上的图案,可以由2^n
* 2^n的情况构造出来。构造方法:左上角和右上角保持不变,左下角顺时针90度,右下角逆时针90度,然后各部分按照虚线相连即可。有n<30,x,y<2^30。输入n和p点坐标(x,
y),答案输出曲线上该点覆盖的格子的序号。注:格子的序号是从(1,1)开始沿着曲线所覆盖的第几个格子。
代码:
#include<stdio.h> long long f(int n, int x, int y) { if(n == 0) return 1; long long m = 1LL << (n-1); if(x <= m && y <= m) return f(n-1, y, x); if(x > m && y <= m) return 3LL * m * m + f(n-1, ,2*m-x+1); //填空 if(x <= m && y > m) return 1LL * m * m + f(n-1, x, y-m); if(x > m && y > m) return 2LL * m * m + f(n-1, x-m, y-m); } int main() { int n, x, y; scanf("%d %d %d", &n, &x, &y); printf("%lld\n", f(n, x, y)); return 0; }
参考答案:m-y+1
【解释】给出的代码是使用分治法,通过分析给出的4个if语句可以判断出依次是处理左下、右下、左上、右上,需要我们填空的部分是处理右下角的。分治法是将大问题变为相同性质的小问题,因此是要将右下角的图形逆时针90度。
假设正方形是m*m,那么顺时针90度,就是将第1行变为第1列,第2行变为第2列,……第m行变为第m列。而逆时针90度,就是将第1行变为第m列,第2行变为第m-1列,……,第m行变为第1列。右下角的部分需要纵坐标平移m个单位后,再逆时针90度。
顺时针90度:(x, y) ==> (y, x)
逆时针90度:(x, y) ==> (m-y+1, m-x+1)
第四题:找环
题意:
编号为1到n的n个点,以及n-1条边构成一棵树。现在在树上加上一条边,这样就构成了一个含环的图了。请你找出该环上的结点,从小到大输出这些结点编号。
测试数据:
30%数据:n<1000
100%数据:n<100000
输入样例:
5
1 2
2 5
4 2
1 3
5 3
输出样例:
1 2 3 5
【题解】
计算出每个点的度数。明显度数为1的点不可能在环上,那么与该点唯一相连的边也就不在环上了,可以删去。删去这条边后,这边的另一个端点度数减去1,如果因此而导致它的度数变为1了,同样说明该点不在环上,可以去掉,不断重复以上操作,直到没有边可以删去。
参考代码中用一个队列来维护边。由于点数n很大,无法使用邻接矩阵,只能使用邻接表来存储图,代码中使用vector容器来实现。
参考代码:
#include<stdio.h> #include<string.h> #include<vector> #include<queue> using namespace std; #define maxn 100010 int d[maxn]; vector<int> e[maxn]; typedef struct EDGE { int u, v; EDGE(int a=0, int b=0):u(a),v(b){} }Edge; queue<Edge> q; int n; void init() { memset(d, 0, sizeof(d)); scanf("%d", &n); int a, b; for(int i = 1; i <= n; i++) { scanf("%d %d", &a, &b); d[a]++; d[b]++; e[a].push_back(b); e[b].push_back(a); q.push(Edge(a,b)); } } void out() { bool flag = false; for(int i = 1; i <= n; i++) if(d[i] > 0) { if(!flag){ printf("%d", i); flag = true; } else printf(" %d", i); } putchar('\n'); } void solve() { while(!q.empty()) { Edge tmp = q.front(); q.pop(); if(d[tmp.u] == 1 || d[tmp.v] == 1) //其中一个端点的度为1时,删去该边 { d[tmp.u]--; d[tmp.v]--; int p; if(d[tmp.u] == 1) { p = tmp.u; int size = e[p].size(); for(int i = 0; i < size; i++) { int t = e[p][i]; if(d[t] > 0) q.push(Edge(p, t)); } } if(d[tmp.v] == 1) { p = tmp.u; int size = e[p].size(); for(int i = 0; i < size; i++) { int t = e[p][i]; if(d[t] > 0) q.push(Edge(p, t)); } } } } } int main() { init(); solve(); out(); return 0; }
第五题:在线匹配
题意:
有n个人在线上玩游戏,每人的积分分别为Ai。如果线上的某两个人的积分恰好相差为k时,他们就会被进行匹配。问线上最多会有多少人,他们任意两人均没法进行匹配?
测试数据:
30%数据:n<10
100%数据:n<100000,Ai<100000,k<100000
输入样例1:
10 0
1 4 2 3 6 7 1 4 2 3
输出样例1:
6
输入样例2:
10 3
1 1 1 1 4 4 1 1 1 1
输出样例2:
8
【题解】
方法一:
30%数据n<10,所以可以用深度搜索,每个数只有选和不选,得到一组待定的数列后,再检查一下是否两两不能匹配,是则用当前数列的个数去尝试更新答案。时间复杂度是O(n*2^n)。
方法二:
将每个数值看作点,把可以匹配的两个数连一条边,那么问题转化为:删去最少的点,使得剩下的点中不存在相连的边。
对于每条边,明显两个端点只能选一个,按照贪心思想,易知应该优先去掉度数多的那个点。
算法步骤:
(1)构图,将积分恰好相差k的两个点相连;
(2)计算出所有点的度数;
(3)找出度数最大的那个点;
(4)把与该点相连的所有点的度数减去1,把该点的度数置为-1表示已经删去该点;
(5)重复(4)(5),直到没有边可以删去;
(6)这时剩余的点的度数均为0,统计出其个数,就是最大不能两两匹配的个数。
时间复杂度为O(n^2)
(n<100000,该方法仍然无法通过所有测试数据,希望有大神能够提供更优秀的方法)
参考代码:
#include<stdio.h> #include<string.h> #include<vector> using namespace std; #define maxn 100010 vector<int> e[maxn]; int a[maxn]; //a[i]表示第i个人的积分 int d[maxn]; //d[i]表示第i个点的度数 bool exist[maxn]; //exist[i]=false表示第i个点删去 int n, k; void init() { scanf("%d %d", &n, &k); for(int i = 1; i <= n; i++) { scanf("%d", &a[i]); } } void out() { int ans = 0; for(int i = 1; i <= n; i++) if(exist[i]) ans++; printf("%d\n", ans); } void solve() { memset(d, 0, sizeof(d)); int i, j; for(i = 1; i < n; i++) for(j = i+1; j <= n; j++) { if(a[i] - a[j] == k || a[j] - a[i] == k) { e[i].push_back(j); e[j].push_back(i); d[i]++; d[j]++; } } memset(exist, true, sizeof(exist)); do{ int maxv = 0, sign = -1; int i; for(i = 1; i <= n; i++) if(exist[i]) { if(d[i] > maxv) maxv = d[sign = i]; } if(maxv == 0) break; else{ int size = e[sign].size(); for(i = 0; i < size; i++) { int p = e[sign][i]; if(exist[p]) d[p]--; } exist[sign] = false; d[sign] = -1; } }while(1); } int main() { init(); solve(); out(); return 0; }
第六题:城市旅游
题意:
有n个城市,火车从一个城市到达另一个与其直接相连的城市,花费时间为1。当处于某个城市时,火车会等概率地随机驶向一个与之相连的城市。火车从任意一个城市开始出发。路线保证没有重边和自环,任意一座城市至少有一个可以到达的城市。请你求出达到每个城市的期望时间。
输入描述
第一行是城市数目n和路线数目m
接下来有m行,每行两个正整数u和v,表示城市u可以到达城市v。
输出描述
答案共n行,每行一个数,分别表示火车达到每个城市的期望时间。
(输入输出样例忘了,仅记得输出样例保留了9位小数)
30%数据:n<10
50%数据:n<15
90%数据:n<19
100%数据:n<=21
【题解】
设Pi为城市i可以到达的城市的数量,Ti为到达城市i的期望时间,则可得到式子
,即,由于任意一个城市至少有一个可达的城市,所以Pi>0。n个城市有n条这样的方程,题目要求Ti。不妨将Ti看作是n个未知数,而1/Pi看作是系数,把数值1放在等号右边,于是就得到n条n元一次方程。于是就可以利用线性代数的知识,使用高斯消元法来解矩阵,求出这n个未知数也就是所有Ti的值了。
(其实这题我也不太确定,因为按照我这个方法的话,出题人大可把n的范围取得大一些。测试样例忘了,我也无法验证自己的代码是否正确。希望大神可以多多指教。)
参考代码:
#include<stdio.h> #include<math.h> #include<string.h> #define maxn 30 typedef double Matrix[maxn][maxn]; int n, m; Matrix A; int p[maxn]; //p[i]表示城市i可达的城市数 bool edge[maxn][maxn]; //edge[u][v]=true表示点u可到达点v void init() { memset(p, 0, sizeof(p)); memset(edge, false, sizeof(edge)); scanf("%d %d", &n, &m); int i, j; int u, v; for(i = 1; i <= m; i++) { scanf("%d %d", &u, &v); // u -> v u--, v--; p[u]++; edge[u][v] = true; } for(i = 0; i < n; i++) for(j = 0; j < n; j++) if(edge[i][j]) //i->j { A[i][j] = 1.0 / p[i]; } for(i = 0; i < n; i++) { A[i][i] = A[i] = -1.0; } } void swap(double &x, double &y) { double t; t = x; x = y; y = t; } //要求系数矩阵可逆 //A是增广矩阵,即A[i] 是第i个方程右边的常数bi void gauss(Matrix A, int n) { int i, j, k, r; for(i = 0; i < n; i++) { r = i; for(j = i + 1; j < n; j++) if(fabs(A[j][i]) > fabs(A[r][i])) r = j; if(r != i) for(j = 0; j <= n; j++) swap(A[r][j], A[i][j]); //与第i+1~n行进行消元 for(k = i+1; k < n; k++) { double f = A[k][i] / A[i][i]; //为了让A[i][i] = 0,第i行所乘的倍数 for(j = i; j <= n; j++) A[k][j] -= f * A[i][j]; } } //回代 for(i = n-1; i >=0; i--) { for(j = i+1; j < n; j++) A[i] -= A[j] * A[i][j]; A[i] /= A[i][i]; } } void outt() { //运行结束后A[i] 是第i个未知数的值 for(int i = 0; i < n; i++) printf("%.9lf\n", A[i] ); putchar('\n'); } int main() { init(); gauss(A, n); outt(); return 0; }
相关文章推荐
- 2017年第八届蓝桥杯省赛B组 C/C++
- (2017)第八届蓝桥杯大赛个人赛省赛(软件类) C/C++ 大学A组 题解(第八题)
- 2017年第八届蓝桥杯试题(C/C++本科B组)9题 分巧克力
- (2017)第八届蓝桥杯大赛个人赛省赛(软件类) C/C++ 大学A组 题解(第一题和第二题)
- (2017)第八届蓝桥杯大赛个人赛省赛(软件类) C/C++ 大学A组 题解(第九题)
- 2017年第八届蓝桥杯 C/C++B组 真题分析与题解(未完待续)
- 第八届蓝桥杯大赛个人赛省赛C++ B组 题解+原题 (填空题)
- 蓝桥杯2017年第八届模拟题C_C++程序设计本科
- 2017年第八届蓝桥杯C/C++B组决赛题解
- (2017)第八届蓝桥杯大赛个人赛省赛(软件类) C/C++ 大学A组 题解(第三题和第四题)
- 第八届蓝桥杯大赛c/c++ b组
- 2015年第六届蓝桥杯本科B组C++省赛个人题解
- 2017第八届蓝桥杯C/C++ B组省赛题解
- 第八届蓝桥杯B组国赛总结
- 第八届蓝桥杯国赛总结 2017/5/27
- 第八届蓝桥杯国赛Java b组 第二题
- 2017第八届蓝桥杯C/C++ B组省赛
- 2017第八届蓝桥杯C/C++ B组省赛题解
- 第八届蓝桥杯国赛心得
- 2017第八届蓝桥杯C/C++ B组省赛题解