【蓝桥杯】真题练习——赢球票

目录

一、题目描述:

二、问题分析

三、代码实现


一、题目描述:

某机构举办球票大奖赛。获奖选手有机会赢得若干张球票。

 主持人拿出 N 张卡片(上面写着 1~N 的数字),打乱顺序,排成一个圆圈。你可以从任意一张

卡片开始顺时针数数: 1,2,3..... 如果数到的数字刚好和卡片上的数字相同,则把该卡片收入囊

中,从下一个卡片重新数数。 直到再无法收获任何卡片,游戏结束。囊中卡片数字的和就是赢得

球票的张数。

比如:
 卡片排列是:1 2 3
 我们从1号卡开始数,就把1号卡拿走。再从2号卡开始,但数的数字无法与卡片对上,很快数字

越来越大,不可能再拿走卡片了。因此这次我们只赢得了1张球票。还不算太坏!如果我们开始就

傻傻地从2或3号卡片数起,那就一张卡片都拿不到了。

如果运气好,卡片排列是 2 1 3

那我们可以顺利拿到所有的卡片!

本题的目标就是:已知顺时针卡片序列。
 

随便你从哪里开始数,求最多能赢多少张球票(就是收入囊中的卡片数字之和)

输入格式

  第一行一个整数N(N<100),表示卡片数目
  第二行 N 个整数,表示顺时针排列的卡片

输出格式

  一行,一个整数,表示最好情况下能赢得多少张球票
 
  用户输入:
  3
  1 2 3

  程序应该输出:
  1


  用户输入:
  3
  2 1 3

  程序应该输出:
  6

二、问题分析

题目文字描述很多,简单分析总结下,从 1-N 中任意选择一个数开始 从1顺时针计数,当计数数值

与卡片上的数值也就是1-N中的数值相等时,就拿走这个卡片(获得卡片上的数值),从下一张卡片

重新从1开始计数,问最大获得卡片的数值和

1. 根据题意,我们首先想到的就是枚举所有可能的情况,求出最大值,这也正是这道题的解题思

路。实际上蓝桥杯枚举的题目经常考察。 明确使用枚举思路后,关键是如何进行枚举,以及枚举

时候的细节。

2.我们从第1张卡片开始进行枚举,并不断进行计数,如果计数和卡片数值相等加上卡片的数值

(也可以是count的值),问题来了,如果满足条件拿走这张卡片,继续枚举时需要跳过这张卡

片,我们如何知道这张卡片已经取走了呢?

3.我们可以使用变量进行标记,但是多个卡片,我们就需要多个变量进行标记,这个时候我们就想

到了使用数组,可以重新开一个新数组专门用来标记,也可以使用记录卡片位置的数组,如果取走卡

片,该位置的值就置为 -1,这样如果在枚举时遇到-1就直接跳过

4.如果使用一个数组,那么我们枚举从某一张卡片开始计数结束之后,我们继续枚举下一张卡片开

始计数,这个时候如果我们肯定不能使用上一次处理后的数组,因此我们需要每次枚举一张开始的

卡片就要重新使用一个临时数组,该数组等于最初输入的卡片顺序

5.每次枚举都使用一个变量sum记录本次获得的数值,本次枚举结束之后进行更新max记录最大值

三、代码实现

Java代码实现

import java.util.Scanner;

class Main{
	//定义全局数组nums 和 数组长度 n 方便函数调用
	 static int[] nums;
	 static int n;
    public static void main(String[] args) {
    	Scanner sc = new Scanner(System.in);
    	 n = sc.nextInt();
    	nums = new int[n];
    	for(int i = 0;i <n;i++) {
    		nums[i] = sc.nextInt();
    	}
    	
    	System.out.println(getMax());
    	  	
    	
    }
	 static int getMax() {
		int max = 0;
		 
		for(int i = 0;i < n;i++) {
			//每次重新选择一行牌作为起始  都要重新设置临时数组
			int[] temp = new int[n];
			//初始化临时数组
			for(int k = 0;k < n;k++) {
				temp[k] = nums[k];
			}
			//计数
			int count = 1;
           //记录一次枚举获得的卡片数值和
			int sum  = 0;
           // 标记是否count大于所有剩余卡片数值,如果true即已经不能再取卡片的跳出循环进行下一 
           // 次枚举
			boolean flag = true;
            // 开始计数的卡片对应的位置下标
			int start = i;
			
			while(true) {
                 // 每次循环重置flag标识
				 flag = true;
                    //判断是否有剩余卡片的数值大于count,如果没有那么不会再取卡片直接跳出循环
				for(int k = 0;k < n; k++) {
					if(temp[k] >= count) {
						flag = false;
					      break;					
					}
				}
				//如果count大于所有数组中的数,那么不能继续数下去了
				if(flag) break;
				
				int j = start ;
				
				//如果计数count 等于 卡片的数值 temp[j]
				if(count == temp[j]) {
					
					sum += count;
					temp[j] = -1;
					count = 1;

				}else {
                //计数count 不等于 卡片的数值 count+1 
					count++;
				}

				//不管是否取走卡片都要继续进行顺时针的下一张卡片
				start = (start+1) % n;

				//num用来记录此时已经取走的卡片数量,防止全部取走后 一直进行while循环
				 int num = 0;

				//跳过已经取走的卡片
				while(temp[start] == -1) {
					start = (start+1) % n;
					num++;

					//防止死循环
					if(num > n ) break;
				}
				
				
			}
			//每次枚举一张卡片开始就更新下 max值
			max = Math.max(max, sum);
			
		}
		
		return max;
	}
}

注意点:

1.我使用的是Java,需要导包,如果不知道导哪个包或者导的包很多,可以直接就导

import java.util.*;

表示util包下的所有子包,其次类的命名必须为 class Main

2.在进行循环选择卡片的时候记得 对n取余,防止数组下标越界

3.每次计数开始都要重新置flag为true,(判断是否有剩余卡片的数值大于count,如果没有,表

示count大于所有卡片数值,不会再有相等的了,不会再取卡片,直接跳出循环)防止上一次循环

的结果影响下一次循环造成死循环

4. 在跳过已经取走的卡片时,需要设置一个变量num记录此时已经取走的卡片数量,如果全部都

取走了就跳出循环,防止死循环


距离蓝桥杯比赛开始还有一个月,为了能够拿奖让学校报销报名费,开始苦练真题,干了!xdm,冲冲冲!!!

  • 5
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
这是一个经典的约瑟夫问,可以使用数学方法进行求解。假设卡片排列是1, 2, 3, ..., N,我们从第K张卡开始数,每次数M个,那么最后剩下的卡片编号为: ((K-1)+M) % N + 1, ((K-1)+2M) % N + 1, ((K-1)+3M) % N + 1, ..., ((K-1)+(N-1)M) % N + 1 为了使得拿到的卡片数最多,我们需要找到一个最大的M,使得所有剩下的卡片编号之和最大。这个问可以通过枚举K和M来解决,时间复杂度为O(N^2)。但是还有更快的算法。 首先,我们可以把问转化为求剩下的卡片编号之和最小。这是因为,如果我们从第K张卡开始数,每次数M个,拿到的卡片编号和为S,那么剩下的卡片编号和就是1+2+...+N-S,也就是(N+1)N/2-S。因此,如果我们能找到一个最小的剩下卡片编号和,就可以通过(N+1)N/2减去它得到拿到的卡片编号和的最大值。 接下来,我们考虑如何快速求解剩下的卡片编号之和的最小值。假设我们从第i张卡开始数,每次数M个,得到的剩余卡片编号为: (i+M-1) % N + 1, (i+2M-1) % N + 1, ..., (i+(N-1)M-1) % N + 1 我们可以把它们按照卡片编号从小到大排序,得到一个新的序列: j1, j2, ..., jN 其中,j1, j2, ..., jN是1, 2, ..., N的一个排列,满足: (i+(j1-1)M) % N + 1 <= (i+(j2-1)M) % N + 1 <= ... <= (i+(jN-1)M) % N + 1 我们把这个排列称为“最小化序列”。注意到,不同的i和M得到的最小化序列可能不同,但是它们都可以通过某种旋转方式相互转化。因此,我们只需要找到最小化序列中的最小值,即可得到剩下的卡片编号之和的最小值。 接下来,我们考虑如何快速求解最小化序列中的最小值。假设最小化序列为: j1, j2, ..., jN 我们把它扩展成一个长度为2N的序列: j1, j2, ..., jN, j1, j2, ..., jN 然后,我们可以使用动态规划的方法求解最小值。令f(i,j)表示从序列中的第i个位置开始,长度为j的子序列中的最小值。显然,f(i,j)可以通过f(i,j/2)和f(i+j/2,j/2)求解。具体地,我们可以考虑分两种情况: 1. 最小值在序列的前半部分。这意味着从第i个位置开始,长度为j的子序列至少包含前半部分的一个元素,即j1, j2, ..., jN中的某个元素。因此,我们可以枚举这个元素,然后在序列的前半部分中查找它的位置,从而得到f(i,j/2)。 2. 最小值在序列的后半部分。这意味着从第i个位置开始,长度为j的子序列至少包含后半部分的一个元素,即j1, j2, ..., jN中的某个元素。因此,我们可以枚举这个元素,然后在序列的后半部分中查找它的位置,从而得到f(i+j/2,j/2)。 最终的答案就是(N+1)N/2减去剩下卡片编号之和的最小值。 代码实现如下:

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

〖雪月清〗

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值