奇怪的比赛
某电视台举办了低碳生活大奖赛。题目的计分规则相当奇怪:
每位选手需要回答10个问题(其编号为1到10),越后面越有难度。答对的,当前分数翻倍;答错了则扣掉与题号相同的分数(选手必须回答问题,不回答按错误处理)。
每位选手都有一个起步的分数为10分。
某获胜选手最终得分刚好是100分,如果不让你看比赛过程,你能推断出他(她)哪个题目答对了,哪个题目答错了吗?
如果把答对的记为1,答错的记为0,则10个题目的回答情况可以用仅含有1和0的串来表示。例如:0010110011 就是可能的情况。
你的任务是算出所有可能情况。每个答案占一行。
多个答案顺序不重要。
答案写在“解答.txt”中,不要写在这里!
参考答案:
0010110011 (0分)
0111010000 (4分)
1011010000 (4分)
解释一下这个每个答案的判分,因为有些题库,样例也是答案的的一个,而选手就可以通过直接输出样例的方法骗分。
为了避免这种情况,现在的比赛是不会选样例选进答案集的。
蓝桥杯很喜欢这种简单的递归题目,有点类似于排列组合的暴力解法,定义一个数组标识每一道题目的正误,然后通过递归来枚举每一种情况,针对每一种情况,算出他的总分,如果等于100分就输出
递归培养的是一种感觉,当年老师讲过这样一句话,可能误导了大家,说递归能完成的循环都可以做,以至于
很多人对这个问题都不重视,其实递归思想才是算法比较核心的一部分,掌握它不论在思想还是实际编码都非常有益。
之前博客中说过递归
递归与动态规划专题 传送门
基础比较薄弱的同学进这里
递归算法视频讲解 bilibili传送门
这里再赘述一下设计递归的三要素
- 子问题
子问题其实就是设计递归函数,我的函数需要什么参数,它要怎么分解原问题 - base case 跳出条件
根据题目,我要在满足什么情况下跳出 - 决策过程
我姑且把决策分为两类
第一类叫做选择,从子问题中选择所需要的结果返回给上一层。类似于求数组最小值,我需要在结果中进行决策选择,选最小的返回。还有李白打酒这种统计结果次数的问题。
第二类叫做回溯,从某个子问题开始就没法得到需要的结果,然后返回到上一层尝试另一个操作,类似本题这样。
那么具体到这道题这三步是怎么完成的。
回答十个问题统计可以分解为得到前九次的结果,然后再加上我当前回答造成的结果
第九个问题可以分解为得到前八次的结果,然后加上我本次,也就是第九次回答得到的结果
子问题分析出来了。递归函数的感觉有了,那么参数的设计呢。我们发现本题有三个值得注意的变量
初始分10 回答问题10次 最终分数100分
我们不断的回答问题,分数一直发生改变,这个首先要设计为参数,其次我们总共回答十次问题,也就是当第十次回答问题的时候,不论结果怎样都要跳出,那么递归函数的另一个参数也找到了。
process(初始分,回答次数);base case
我们发现,之前三个变量还有一个没用到,就是最终分数,我们题意要我们得到的是最终分数为100分的序列
当我们拿到所有可能的遍历结果后,我们需要找出最终结果正好等于100的遍历结果。
根据题意的运算规则运算每个序列,得到100的将它输出决策,这就是那种回溯决策问题,画出解答树,每一次选择对应错误和正确两种结果我们的决策其实就是遍历解答树,然后把满足条件的输出,其实这一步已经包含在base case里了
补充
剪枝,剪枝是要根据题意分析的,我们根据评分策略发现,这个分数是整体上升趋势的,如果要得到100分为最终结果 那么最多上升到155,因为如果答错题扣分,1~10的累加和为55,要得到结果100。之前的最大值不可能超过55+100也就是155,之后无论如何都减不到答案,这样的无效部分可以省略。本来这道题的解空间是2^10,是指数级的
通过剪枝可以大大减少无效部分的运算以节省开销
解法一 用数组记录答案
/*
* 用一个静态布尔数组标记对错题序列
*
* 子问题参数设计为(当前总分,第几次答题)
* base case 为第10次答题,总分为100的时候只需要把静态数组打印就行
*/
public class Main {
static boolean vis[]=new boolean[10];
public static void main(String[] args) {
process(10, 0);//第1次,初始分10分
}
public static void process(int score,int t){//总分和第几次答题
if (t==10) {//base case
if (score==100) {
for (int i = 0; i < vis.length; i++) {
if (vis[i]) {
System.out.print(1);
}else {
System.out.print(0);
}
}
System.out.println();
}
return ;
}
// if (score>155) {//剪枝
// return;
// }
//从下标0位置开始,递归遍历所有结果,解答树每次都有两个分支,答对或答错
vis[t]=true;
process(score*2, t+1);
vis[t]=false;
process(score-(t+1), t+1);
return ;
}
}
解法二 用字符串记录答案
/*
* 把每个字符串遍历出来(递归拼接到长度为10),每个长度为10的结果出来就踩中check函数,然后根据结果判断是否满足最终成绩为100的情况+4
*/
public class Main {
public static void main(String[] args) {
String str="";
process(str, 0);
}
public static void process(String str,int t){//总分和第几次答题
if (t==10) {
check(str);
return ;
}
process(str+1, t+1);
process(str+0, t+1);
return ;
}
public static void check(String str) {
int score=10;
for (int i = 0; i < str.length(); i++) {
if (str.charAt(i)=='0') {
score-=(i+1);
}else {
score*=2;
}
if (score>155) {//剪枝
break;
}
}
if (score==100) {
System.out.println(str);
}
}
}