蓝桥杯2015年省赛[第六届]-JavaB组赛题解析
参考蓝桥杯官网给出的赛题和官方给出的代码。
蓝桥杯官方讲解视频:https://www.lanqiao.cn/courses/2737
真题文档:https://www.lanqiao.cn/courses/2786/learning/?id=67104
时间:4小时
总分:150分
不想做全套题的可以考虑4,7,9,10题
题1.三角形面积[填空][3分]
1.题目描述
如图1所示。图中的所有小方格面积都是1。
那么,图中的三角形面积应该是多少呢?
请填写三角形的面积。不要填写任何多余内容或说明性文字。

2.简要分析
- 矩形面积减去三个三角形面积。(64-36=28)
3.答案
28
题2.立方变自身[填空][5分]
1.题目描述
观察下面的现象,某个数字的立方,按位累加仍然等于自身。
1^3 = 1
8^3 = 512 5+1+2=8
17^3 = 4913 4+9+1+3=17
…
请你计算包括1,8,17在内,符合这个性质的正整数一共有多少个?
请填写该数字,不要填写任何多余的内容或说明性的文字。
2.简要分析
- 题目很简单,不过有点模糊的就是这个正整数的范围。
- 如果硬做的话,我们循环到9999或者99999,看看最后的个数是否稳定就可以了。
- 这里我们可以简单的推理一下,假设这个数字是个三位数,那么100^3=1000000,是个七位数,七位数相加,最大可能是7*9=63,已经小于100了,所有三位数就是不可能的了,我们只需要循环到99即可。
3.实现代码(模拟)
public class _02立方变自身 {private static int ans;public static void main(String[] args) {for (int i = 1; i <99; i++) {int i1 = i * i * i;int sum=sum(i1);if (sum==i){System.out.println(i+" "+i1);ans++;}}System.out.println(ans);}private static int sum(int x) {String s=String.valueOf(x);int sum=0;for (int i = 0; i < s.length(); i++) {sum+=s.charAt(i)-'0';}return sum;}
}//6
4.答案
6
题3.三羊献瑞[填空][9分]
1.题目描述
观察下面的加法算式:
祥 瑞 生 辉+ 三 羊 献 瑞
-------------------三 羊 生 瑞 气

其中,相同的汉字代表相同的数字,不同的汉字代表不同的数字。
请你填写“三羊献瑞”所代表的4位数字(答案唯一),不要填写任何多余内容。
2.简要分析
- 初步的看一下这个算式,可以基本确定,
三就是1(两位数加法进位),那么祥就是9,羊就是0,后面似乎无法确定下来,不过也可以得到一些大小关系,无妨用字母替代:
a b c d+ e f g b
-------------------e f c b ie=1,a=9,f=0,c=b+1,c+g>10
- 我们可以枚举剩下的数字
b,d,g,i。这四个数字不相等,且不可能是已经确定了的0,1,9。
3.实现代码(枚举)
public class _03三羊献瑞 {public static void main(String[] args) {for (int b = 2; b < 9; ++b) {for (int d = 2; d < 9; ++d) {if (b == d) continue;for (int g = 2; g < 9; ++g) {if (g == b || g == d) continue;int c = b + 1;if (c == b || c == d || c == g) continue;if (c + g <= 10) continue;
/*a b c d+ e f g b
-------------------e f c b ie=1,a=9,f=0,c=b+1,c+g>10*/int sum = 9000 + b * 100 + c * 10 + d + 1000 + g * 10 + b;for (int i = 2; i < 9; ++i) {if (i == b || i == d || i == g || i == c) continue;if (sum == (10000 + c * 100 + b * 10 + i)) {System.out.printf("%2d%d%d%d\n", 9, b, c, d);System.out.printf("%2d%d%d%d\n", 1, 0, g, b);System.out.printf("---------\n");System.out.printf("%d\n", sum);}}}}}}
}
4.答案
1085
题4.循环节长度[代码填空][11分]
1.题目描述
两个整数做除法,有时会产生循环小数,其循环部分称为:循环节。
比如,11/13=6=>0.846153846153… 其循环节为[846153] 共有6位。
下面的方法,可以求出循环节的长度。
请仔细阅读代码,并填写划线部分缺少的代码。
public static int f(int n, int m){n = n % m;//11Vector v = new Vector();for(;;){v.add(n);//11 6n *= 10;//110 60n = n % m;//6if(n==0) return 0;if(v.indexOf(n)>=0) _________________________________ ; //填空}}
注意,只能填写缺少的部分,不要重复抄写已有代码。不要填写任何多余的文字。
2.简要分析
-
分析这段代码,先以一种大概猜测的形式去做,经过一些运算,将一些数字加入向量中,然后如果发现n已经在v里面有了,应该执行一个什么样的操作。大胆猜测,肯定就是这个时候找到了循环节,那么这个向量里面装的应该就是小数部分,循环节的长度应该等于此时v的长度减去第一次出现n的位置(下标)。
-
当然上面只是我们的一种猜测,接下来仔细分析一下代码。用11和13模拟一下。
- 首先n=11%13=11。
- 然后创建了一个vector。接下来一直循环。
- 将n放入v,此时的v:
11 - n=110。
- n=110%13=6。
- 将n放入v,此时v:
11,6。 - n=60。
- n=60%13=8。
- 将n放入v,此时v:
11,6,8。 ..........
-
通过上面的模拟我们可以发现,题目中的代码实际上是在模拟除的过程,不管它关注的只是余数,v里面存的也是余数,这样我们就可以发现,如果在计算过程中遇到两个一样的于是,因为m是固定的,按照问我们常规的除法计算,这里肯定是存在循环的,中间每一个余数对应的是一位小数,所以循环节的长度就是v的长度减去n第一次在v中出现的位置(下标)。
-
我们第一次的大胆猜测,虽然答案没有错,但其实原理是不对的,哈哈哈。
3.实现代码
public class _04_循环节长度 {public static int f(int n, int m) {n = n % m;Vector v = new Vector();for (; ; ) {v.add(n);n *= 10;n = n % m;if (n == 0) return 0;if (v.indexOf(n) >= 0)return v.size() - v.indexOf(n); //填空}}public static void main(String[] args) {System.out.println(f(11, 13));System.out.println(f(7, 18));System.out.println(11%13);}
}
4.答案
return v.size() - v.indexOf(n)
题5.九数组分数[代码填空][15分]
1.题目描述
1,2,3…9 这九个数字组成一个分数,其值恰好为1/3,如何组法?
下面的程序实现了该功能,请填写划线部分缺失的代码。
public class A
{public static void test(int[] x){int a = x[0]*1000 + x[1]*100 + x[2]*10 + x[3];int b = x[4]*10000 + x[5]*1000 + x[6]*100 + x[7]*10 + x[8];if(a*3==b) System.out.println(a + " " + b);}public static void f(int[] x, int k){if(k>=x.length){test(x);return;}for(int i=k; i<x.length; i++){{int t=x[k]; x[k]=x[i]; x[i]=t;}f(x,k+1);_______________________________________ // 填空}}public static void main(String[] args){int[] x = {1,2,3,4,5,6,7,8,9};f(x,0);}
}
注意,只能填写缺少的部分,不要重复抄写已有代码。不要填写任何多余的文字。
2.简要分析
- 读完题,发现是一个全排列问题,再看代码,果然是,看空缺的地方,其实就是全排列中恢复两个交换值的地方。
- 需要特别注意的是,上面的交换代码用括号括起来了,里面声明的变量t不能在下面直接使用!
3.实现代码
public class _05九数组分数 {public static void test(int[] x){int a = x[0]*1000 + x[1]*100 + x[2]*10 + x[3];int b = x[4]*10000 + x[5]*1000 + x[6]*100 + x[7]*10 + x[8];if(a*3==b) System.out.println(a + " " + b);}public static void f(int[] x, int k){if(k>=x.length){test(x);return;}for(int i=k; i<x.length; i++){{int t=x[k]; x[k]=x[i]; x[i]=t;}f(x,k+1);{int t=x[k]; x[k]=x[i]; x[i]=t;} // 填空}}public static void main(String[] args){int[] x = {1,2,3,4,5,6,7,8,9};f(x,0);}
}
4.答案
{int t=x[k]; x[k]=x[i]; x[i]=t;}
题6.加法变乘法[填空][17分]
1.题目描述
我们都知道:1+2+3+ … + 49 = 1225 现在要求你把其中两个不相邻的加号变成乘号,使得结果为2015
比如: 1+2+3+…+1011+12+…+2728+29+…+49 = 2015 就是符合要求的答案。
请你寻找另外一个可能的答案,并把位置靠前的那个乘号左边的数字提交(对于示例,就是提交10)。
注意:需要你提交的是一个整数,不要填写任何多余的内容。
2.简要分析
- 双层循环暴力枚举两个乘号的位置即可。
3.实现代码(暴力枚举)
public class _06_加法变乘法 {public static void main(String[] args) {for (int i = 1; i <= 46; i++) {for (int j = i + 2; j <= 48; j++) {if (i * (i + 1) - (i + i + 1) + j * (j + 1) - (j + j + 1) == 2015 - 1225)System.out.println(i + " " + j);}}}
}
4.答案
16
题7.牌型种数[填空][21分]
1.题目描述
小明被劫持到X赌城,被迫与其他3人玩牌。
一副扑克牌(去掉大小王牌,共52张),均匀发给4个人,每个人13张。
这时,小明脑子里突然冒出一个问题:
如果不考虑花色,只考虑点数,也不考虑自己得到的牌的先后顺序,自己手里能拿到的初始牌型组合一共有多少种呢?
请填写该整数,不要填写任何多余的内容或说明文字。
2.简要分析
- 其实就是无顺序的全排列问题,也可以理解为DFS的一种应用。
- 直接看代码。
- 二维动态规划解决方法:牌型种数问题–二维动态规划解决(附DFS)
3.实现代码(无顺序全排列)
public class _07_牌型种数 {private static int ans;public static void main(String[] args) {f(0, 0);System.out.println(ans);}private static void f(int k, int cnt) {if (k > 13 || cnt > 13) return;if (k == 13 && cnt == 13) {ans++;return;}for (int i = 0; i < 5; i++) {f(k + 1, cnt + i);}}
}
4.答案
3598180
题8.饮料换购[13分]
1.题目描述
乐羊羊饮料厂正在举办一次促销优惠活动。乐羊羊C型饮料,凭3个瓶盖可以再换一瓶C型饮料,并且可以一直循环下去,但不允许赊账。
请你计算一下,如果小明不浪费瓶盖,尽量地参加活动,那么,对于他初始买入的n瓶饮料,最后他一共能得到多少瓶饮料。
输入:一个整数n,表示开始购买的饮料数量(0
输出:一个整数,表示实际得到的饮料数
例如:
用户输入:
100
程序应该输出:
149
用户输入:
101
程序应该输出:
151
资源约定:
峰值内存消耗(含虚拟机) < 256M
CPU消耗 < 1000ms
请严格按要求输出,不要画蛇添足地打印类似:“请您输入…” 的多余内容。
所有代码放在同一个源文件中,调试通过后,拷贝提交该源码。
注意:不要使用package语句。不要使用jdk1.7及以上版本的特性。
注意:主类的名字必须是:Main,否则按无效代码处理。
2.简要分析
- 简单模拟一下换饮料的过程即可。
- 初始总共得到的饮料数为n。
- 当n>=3时,每换一次饮料相当于n-=2,总共得到的饮料数+1。
3.实现代码(简单模拟)
public class Main {public static void main(String[] args) {Scanner sc = new Scanner(System.in);int n = sc.nextInt();int ans = n;while (n >= 3) {n -= 2;ans += 1;}System.out.println(ans);}
}
题9.垒骰子[25分]
1.题目描述
赌圣atm晚年迷恋上了垒骰子,就是把骰子一个垒在另一个上边,不能歪歪扭扭,要垒成方柱体。
经过长期观察,atm 发现了稳定骰子的奥秘:有些数字的面贴着会互相排斥!
我们先来规范一下骰子:1 的对面是 4,2 的对面是 5,3 的对面是 6。
假设有 m 组互斥现象,每组中的那两个数字的面紧贴在一起,骰子就不能稳定的垒起来。 atm想计算一下有多少种不同的可能的垒骰子方式。
两种垒骰子方式相同,当且仅当这两种方式中对应高度的骰子的对应数字的朝向都相同。
由于方案数可能过多,请输出模 10^9 + 7 的结果。
不要小看了 atm 的骰子数量哦~
「输入格式」
第一行两个整数 n m
n表示骰子数目
接下来 m 行,每行两个整数 a b ,表示 a 和 b 不能紧贴在一起。
「输出格式」
一行一个数,表示答案模 10^9 + 7 的结果。
「样例输入」
2 1
1 2
「样例输出」
544
「数据范围」
对于 30% 的数据:n <= 5
对于 60% 的数据:n <= 100
对于 100% 的数据:0 < n <= 10^9, m <= 36
资源约定:
峰值内存消耗(含虚拟机) < 256M
CPU消耗 < 2000ms
请严格按要求输出,不要画蛇添足地打印类似:“请您输入…” 的多余内容。
所有代码放在同一个源文件中,调试通过后,拷贝提交该源码。
注意:不要使用package语句。不要使用jdk1.7及以上版本的特性。
注意:主类的名字必须是:Main,否则按无效代码处理。
2.简要分析
-
题目很好理解,就是n个骰子,在满足对应的面不贴在一起的时候,总共有多少种可能性。大概方法分析一下:
- 首先,很好理解的就是,骰子的边上四个面都可以旋转,所以在垒好n个骰子之后,因为旋转侧面,总的可能数会是4的n次方,这样只要计算不需要考虑旋转情况下垒好n个骰子的可能数m,最后的结果就是4的m次方。不过由于这个数或很大,所以最好使用快速幂的方法去计算这个结果。
- 我们可以拿例子来简单的看一下,两个骰子不考虑四边旋转的时候,可能数是6*6-2=34(有两个会冲突),然后再乘上2的四次方16,最后的结果就是544.
- 对于每一个骰子,都有6个面,每一个面都可以做为一个朝上的面与其他面接触(需要排除产生冲突的情况)。所以,用递归或者迭代的方式,都是可以做出来的。但是数据的规模达到了10的9次方,这两种方法肯定都会超时。
- 这题的标准思路是矩阵的方法。
-
下面是三种实现方式,只有矩阵快速幂的方式是可以完全通过测试案例的。
3.实现代码1(递归)(会超时)
- 递归实现的方式很好理解,从第n个骰子开始,向下递归,如果冲突就不计算,出口是cnt=0,也就是递归到第一个骰子,此时返回1.
- 但是多达10^9的递归,毫无疑问会超时。
- 递归方面,也没得好的优化办法。
public class _2015_b9_1 {static int op[] = new int[7];static long conflict[][] = new long[7][7];private static int n;private static int m;private static final long MOD = 1000000007;//初始化骰子的对立面static void init() {op[1] = 4;op[4] = 1;op[2] = 5;op[5] = 2;op[3] = 6;op[6] = 3;}public static void main(String[] args) {init();Scanner sc = new Scanner(System.in);n = sc.nextInt();m = sc.nextInt();//初始化冲突矩阵 - 1代表没有冲突for (int i = 0; i <=6; i++) {for (int j = 0; j<=6; j++) {conflict[i][j]=1;}}//建立冲突矩阵for (int i = 0; i < m; i++) {int a = sc.nextInt();int b = sc.nextInt();//0代表有冲突的面conflict[op[a]][b] = 0;conflict[op[b]][a] = 0;}long ans=0;for(int i=1;i<=6;i++){ans=(ans+f(i,n-1))%MOD;}System.out.println(ans * power(4, n) % MOD);}private static long f(int up,int cnt){if(cnt==0){return 1;}long ans=0;for(int i=1;i<=6;i++){if(conflict[op[up]][i]==0) continue;ans+=f(i,cnt-1);}return ans;}//快速幂private static long power(long i, int n) {long ans = 1;while (n != 0) {if ((n & 1) == 1) ans = (ans * i) % MOD;i = i * i % MOD;n >>= 1;}return ans;}
}
4.实现代码2(动态规划)(会超时)
- dp的方式需要能找出合适的递推关系。
- 通过递归的思路,我们可以得出如下的递推关系式:
- 假设
dp[i][j]表示第i层,限定朝上的数字为j的方案数。 - 那么我们可以得到递推关系式子:
dp[i][j]=(求和x从1到6)dp[i-1][x] | x与op[j]不冲突。 - 因为每一层都只和上一层有关系,所以dp的第一维的长度可以缩小,能够减少大量空间,只需
dp[2][7]即可。 - 这个dp还是比较好理解的。
- 但是时间复杂度达到O(36n),还是会超时。
public class _2015_b9_2 {static int op[] = new int[7];static long conflict[][] = new long[7][7];static long dp[][]=new long [2][7];private static int n;private static int m;private static final long MOD = 1000000007;//初始化骰子的对立面static void init() {op[1] = 4;op[4] = 1;op[2] = 5;op[5] = 2;op[3] = 6;op[6] = 3;}public static void main(String[] args) {init();Scanner sc = new Scanner(System.in);n = sc.nextInt();m = sc.nextInt();//初始化冲突矩阵 - 1代表没有冲突for (int i = 0; i <=6; i++) {for (int j = 0; j<=6; j++) {conflict[i][j]=1;}}//建立冲突矩阵for (int i = 0; i < m; i++) {int a = sc.nextInt();int b = sc.nextInt();//0代表有冲突的面conflict[op[a]][b] = 0;conflict[op[b]][a] = 0;}for(int i=1;i<=6;i++){dp[0][i]=1;}int cur=0;//最外层循环-迭代的层数for(int floor=2;floor<=n;floor++){//滚动数组cur=1-cur;//尝试将六面作为朝上面for(int j=1;j<=6;j++){dp[cur][j]=0;//累加不冲突的面for(int i=1;i<=6;i++){//有冲突if(conflict[op[j]][i]==0) continue;dp[cur][j]=(dp[cur][j]+dp[1-cur][i])%MOD;}}}long ans=0;for(int k=1;k<=6;k++){ans=(ans+dp[cur][k])%MOD;}System.out.println(ans * power(4, n) % MOD);}//快速幂private static long power(long i, int n) {long ans = 1;while (n != 0) {if ((n & 1) == 1) ans = (ans * i) % MOD;i = i * i % MOD;n >>= 1;}return ans;}
}
5.实现代码3(矩阵快速幂)
- 用矩阵实现的关键在于要能发现:(冲突矩阵的n-1次方)*(6行全1列向量)=第一个骰子每一个面可能种数构成的矩阵。
- 具体如何发现的,可以根据dp关系推导递推矩阵,也可以从头开始尝试。
- 矩阵的n-1次方可以使用矩阵快速幂的方式实现,这样时间复杂度就可以降下来了。
public class Main {static int op[] = new int[7];private static int n;private static int m;private static final long MOD = 1000000007;//初始化骰子的对立面static void init() {op[1] = 4;op[4] = 1;op[2] = 5;op[5] = 2;op[3] = 6;op[6] = 3;}public static void main(String[] args) {init();Scanner sc = new Scanner(System.in);n = sc.nextInt();m = sc.nextInt();long conflict[][] = new long[6][6];//初始化冲突矩阵 - 1代表没有冲突for (int i = 0; i < 6; i++) {for (int j = 0; j < 6; j++) {conflict[i][j]=1;}}//建立冲突矩阵for (int i = 0; i < m; i++) {int a = sc.nextInt();int b = sc.nextInt();//0代表有冲突的面conflict[op[a] - 1][b - 1] = 0;conflict[op[b] - 1][a - 1] = 0;}// 求冲突矩阵的n-1次方long[][] mPow_n_1 = mPow(conflict, n - 1);//累加矩阵的每个元素long ans = 0;//枚举第一个骰子和第二个骰子面相邻的所有可能for (int i = 0; i < 6; i++) {for (int j = 0; j < 6; j++) {ans = (ans + mPow_n_1[i][j]) % MOD;}}//ans*4^nSystem.out.println(ans * power(4, n) % MOD);}//快速幂private static long power(long i, int n) {long ans = 1;while (n != 0) {if ((n & 1) == 1) ans = (ans * i) % MOD;i = i * i % MOD;n >>= 1;}return ans;}/*矩阵的快速幂*/private static long[][] mPow(long[][] conflict, int n) {long[][] e = new long[6][6];for (int i = 0; i < 6; i++) {for (int j = 0; j < 6; j++) {if (i == j) e[i][j] = 1;else e[i][j] = 0;}}while (n != 0) {if ((n & 1) == 1) {e = mMul(e, conflict);}conflict = mMul(conflict, conflict);n >>= 1;}return e;}//矩阵乘法private static long[][] mMul(long[][] a, long[][] b) {long[][] ans = new long[6][6];for (int i = 0; i < 6; i++) {for (int j = 0; j < 6; j++) {for (int k = 0; k < 6; k++) {ans[i][j] = (ans[i][j] + a[i][k] * b[k][j]) % MOD;}}}return ans;}
}
题10.生命之树[31分]
1.题目描述
在X森林里,上帝创建了生命之树。
他给每棵树的每个节点(叶子也称为一个节点)上,都标了一个整数,代表这个点的和谐值。
上帝要在这棵树内选出一个非空节点集S,使得对于S中的任意两个点a,b,都存在一个点列 {a, v1, v2, …, vk, b} 使得这个点列中的每个点都是S里面的元素,且序列中相邻两个点间有一条边相连。
在这个前提下,上帝要使得S中的点所对应的整数的和尽量大。
这个最大的和就是上帝给生命之树的评分。
经过atm的努力,他已经知道了上帝给每棵树上每个节点上的整数。但是由于 atm 不擅长计算,他不知道怎样有效的求评分。他需要你为他写一个程序来计算一棵树的分数。
「输入格式」
第一行一个整数 n 表示这棵树有 n 个节点。
第二行 n 个整数,依次表示每个节点的评分。
接下来 n-1 行,每行 2 个整数 u, v,表示存在一条 u 到 v 的边。由于这是一棵树,所以是不存在环的。
「输出格式」
输出一行一个数,表示上帝给这棵树的分数。
「样例输入」
5
1 -2 -3 4 5
4 2
3 1
1 2
2 5
「样例输出」
8
「数据范围」
对于 30% 的数据,n <= 10
对于 100% 的数据,0 < n <= 10^5, 每个节点的评分的绝对值不超过 10^6 。
资源约定:
峰值内存消耗(含虚拟机) < 256M
CPU消耗 < 3000ms
请严格按要求输出,不要画蛇添足地打印类似:“请您输入…” 的多余内容。
所有代码放在同一个源文件中,调试通过后,拷贝提交该源码。
注意:不要使用package语句。不要使用jdk1.7及以上版本的特性。
注意:主类的名字必须是:Main,否则按无效代码处理。
2.简要分析
- 由题意可以知道,这是一棵无根树。
- 对于无根树,我们可以将每一个节点做为根节点去计算最大的值,最后取所有可能里面的最大值。
- 具体实现见代码。
3.实现代码
public class Main {private static int n;//存储所有节点的权值private static long[] w;//private static List<Integer>[] g;private static long ans ;public static void main(String[] args) throws FileNotFoundException {Scanner sc = new Scanner(System.in);n = sc.nextInt();w = new long[n + 1];g = new ArrayList[n + 1];initG();for (int i = 1; i <= n; i++) {w[i] = sc.nextLong();}for (int i = 0; i < n - 1; i++) {int a = sc.nextInt();int b = sc.nextInt();g[a].add(b);g[b].add(a);}dfs(1, 0);System.out.println(ans);}/*** root做为根所代表的子树有一个最大权和,将其存储在w[root]中* @param root* @param fa*/private static void dfs(int root, int fa) {//遍历每一个相连的节点for (int i = 0; i < g[root].size(); i++) {Integer child = g[root].get(i);if (child == fa) continue;//递归计算dfs(child, root);if (w[child] > 0)w[root] += w[child];}//不断更新最大值if (w[root] > ans) ans = w[root];}//初始化二维集合gprivate static void initG() {for (int i = 0; i < n + 1; i++) {g[i] = new ArrayList<Integer>();}}
}
总结
-
做一遍下来头晕了~~~
-
1-8题难度不大,最后两题还是有一定难度的。
-
梳理一下考点:
- 1.暴力枚举。
- 2.代码分析。
- 3.无序的全排列。
- 4.经典全排列。
- 5.过程模拟。
- 6.矩阵快速幂。
- 7.无根树的转换。
-
矩阵快速幂和无根树的转换是这套题目的重点了。
ATFWUS Writing 2021-1-23
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
