模拟算法.

一、引言:

模拟算法,简单来说,就是按照题目描述的步骤或规则,一步一步地用代码实现解决问题的过程。就像是你在玩一个游戏,游戏有它自己的规则,而你需要根据这些规则来做出相应的动作以完成特定的目标。

二、模拟算法的特点与技巧:

特点:

  • 代码量大(做好准备( •̀ ω •́ )✧迎接挑战)
  • 操作多
  • 思路复杂
  • 较为复杂的题,出错后难定位错误

技巧:

  • 动手前,尽量在电脑或演草纸列好思路
  • 代码中,尽量对代码模块化 ,写成函数,类(Java)/结构体(C++)
  • 对于一些会重复用到的概念,可以统一转化,方便处理
  • 调试代码时,可分块调试(对代码模块化的好处)
  • 写代码一定要思路清晰,不要想到啥写啥,按自己提前列好的思路书写

蓝桥常考题型:

基础模拟(字符串处理、日期处理),状态模拟,过程模拟等... 其中以基础模拟最常见。

笔者提醒:

模拟这个算法其实非常难,主要是逻辑上的麻烦 ,但正常刷题时我们都不把模拟的逻辑思维理清就直接做,如果这题没有太水的话,是非常容易错的。

模拟可以与任何算法同时出现,比如模拟与动归dp、模拟与搜索之类的。

而接下来的入门题型 ,就是"水题"!基础与进阶代码量会大一些。但这没啥好担心的,这毕竟是算法能力提升的基石-敲代码能力( ̄︶ ̄)↗ 。

三、练习:

模拟类型、是以编程题考察。

提示( •̀ ω •́ )✧

至少要独立思考10min,若20min后仍无思路在看解析,收获会更大!

入门

1、单身贵族游戏解析

2、简简单单的暴力题解析

3、小浩的国际象棋解析

4、缩位求和(蓝桥真题)(解析

5、Goshopping解析

基础

6、交换瓶子(蓝桥真题)(解析

7、奇怪的数列(蓝桥真题)(解析

8、长草(蓝桥真题)(解析

基础-日期系列

9、跑步(蓝桥真题)

10、跑步计划(蓝桥真题)

11、定时任务(蓝桥真题)

进阶

12、赢球票 (蓝桥真题)

13、神奇闹钟(蓝桥真题)

【最后一题】拉马车 (蓝桥真题)

入门:

1、单身贵族游戏

问题描述

单身贵族游戏规则:游戏玩法似跳棋,但不能走步,只能跳;棋子只能跳过相邻的棋子(相邻位置上一定要有棋子)到空位上,并且把被跳过的棋子吃掉;棋子可以沿格线横、纵方向跳,但是不能斜跳。

在本题中,给定单身贵族游戏走若干步后的棋盘状态(不用判断是否合理),判断游戏是否已经结束了(即不能再走下去了)。

以下图(a)为单身贵族游戏的棋盘,图(b)演示了走棋的规则,图(c)所示的棋盘状态已经结束了,无法再走棋。

输入格式

输入数据占 77 行,描述了一个单身贵族游戏的棋盘状态。注意第 11、22、66、77 行的数据也是顶格的(在程序处理时,需要还原到棋盘中的位置)。每个位置上为 11 或 00,前者表示有棋子,后者表示没有。

输出格式

测试数据所表示的单身贵族游戏,如果游戏无法进行下去了,输出 yes,否则输出 no。

样例输入

000
001
0000001
0000000
0000101
000
000

样例输出

yes

Java版

java 复制代码
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        // 定义一个 7x7 的二维数组,初始化为 0
        int[][] vec = new int[7][7];
        
        // 创建 Scanner 对象,用于读取输入
        Scanner scanner = new Scanner(System.in);
        
        // 读取 7 行字符串,并将其转换为数字存储到二维数组中
        for (int i = 0; i < 7; ++i) {
            String str = scanner.next();
            // 如果字符串长度为 3,将其存储到数组的第 2 到第 4 列
            if (str.length() == 3) {
                for (int j = 0; j < 3; ++j) {
                    vec[i][j + 2] = str.charAt(j) - '0'; // 将字符转换为数字
                }
            } else {
                // 如果字符串长度为 7,将其存储到数组的第 0 到第 6 列
                for (int j = 0; j < 7; ++j) {
                    vec[i][j] = str.charAt(j) - '0'; // 将字符转换为数字
                }
            }
        }
        
        // 定义一个布尔变量,用于判断游戏是否可以继续进行
        boolean flag = true;
        
        // 遍历数组的中间部分(第 1 到第 5 行和列)
        for (int i = 1; i < 6; ++i) {
            for (int j = 1; j < 6; ++j) {
                // 如果当前元素为 1,表示有一个棋子
                if (vec[i][j] == 1) {
                    // 检查当前棋子的上下左右是否有其他棋子
                    if (vec[i][j - 1] == 1 || vec[i][j + 1] == 1 || vec[i - 1][j] == 1 || vec[i + 1][j] == 1) {
                        flag = false; // 如果有相邻的棋子,游戏可以继续进行
                    }
                }
            }
        }
        
        // 如果 flag 为 true,表示游戏无法继续进行,输出 "yes"
        if (flag) {
            System.out.println("yes");
        } else {
            // 否则,输出 "no"
            System.out.println("no");
        }
    }
}

----------------- scanner.next()方法的作用 ----------------------

在 Java 中,String str = scanner.next(); 是用来从输入中读取一个字符串的。
具体来说,scanner.next() 方法会读取输入中的下一个单词(即由空格分隔的字符串),
并将其存储在变量 str 中。

C++版

cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;
int main()
{
    // 就是只是一个暴力问题
    vector<vector<int>> vec(7,vector<int>(7,0));
    for(int i=0; i<7; ++i){
        string str;
        cin>>str;
        // 将所有数字存入
        if(str.size()==3){
            for (int j = 0; j < 3; ++j) {
                vec[i][j+2] = str[j]-'0'; // 输入题目喽
            }
        }else{
            for (int j = 0; j < 7; ++j) {
                vec[i][j] = str[j]-'0';
            }
        }
    }
    // 改判断这个数字是否符合条件
    // 大致思路就是,只要所有棋,四周都没有其他棋,就算结束
    // 只要1附近没有1,就算结束
    bool flag = true;
    for(int i=1; i<6; ++i){
        for(int j=1; j<6; ++j){
            if(vec[i][j]==1){
                // 通过,对每一个棋子的四周进行判断,下、上、左、右
                if(vec[i][j-1]==1||vec[i][j+1]==1||vec[i-1][j]==1||vec[i+1][j]==1){
                    flag = false;
                }
            }
        }
    }
    // 注意,是游戏无法进行下去!才是yes
    if(flag) cout<<"yes"<<endl;
    else cout<<"no"<<endl;


    return 0;
}

2、简简单单的暴力题

问题描述

有一个长为 nn 的序列 a1,a2.....ana1​,a2​.....an​ 与一个特殊的数 kk ,aiai​ 小于 kk 。

当一个序列(元素个数 size≥2size≥2 )中有一个数 xx 会等于其他数的总和 sumsum % kk ,即 sumsum % k==xk==x ,则说明此序列为好序列。例如 kk 为 55 ,一个序列为 [1,2,4][1,2,4] ,因为 (2+4)(2+4) % 5==15==1 所以他是一个好序列。

求在长度为 nn 的序列中有多少连续的子序列为好序列。

输入格式

输入共两行。

第一行包含两个数 nn , kk ,表示序列长和特殊的数。

第二行包含 nn 个小于k的非负整数 a1,a2,a3,...,ana1​,a2​,a3​,...,an​ 。

输出格式

输出一个数 sumsum 为好序列个数。

样例输入

3 3
2 2 1

样例输出

2

评测数据规模

对于 100100 %案例 1≤n≤10001≤n≤1000 , 1≤k≤101≤k≤10 。

样例说明

1≤l≤r≤31≤l≤r≤3 ,

l=1,r=1l=1,r=1 ,序列为: 22 不符合条件 不是好序列,

l=1,r=2l=1,r=2 ,序列为: 2,22,2 是好序列 2mod3==22mod3==2 ,

l=1,r=3l=1,r=3 ,序列为: 2,2,12,2,1 是好序列 (2+1)mod3==1(2+1)mod3==1 ,

l=2,r=2l=2,r=2 ,序列为: 22 不符合条件,不是好序列,

l=2,r=3l=2,r=3 ,序列为:2,12,1 不符合条件,不是好序列,

故好序列个数为 22 。

Java版

java 复制代码
import java.util.Scanner;
// 本题的坑,真是大大的
// 注意!题目上说了,连续的子序列!切记是序列中衍生出来的子序列。
// 具体结合 '样例说明',l代表起始位置,r代表结束位置
// 切记!本题for循环不宜过多套用,否则将会导致超时
public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        // 读取序列长度 n 和特殊的数 k
        int n = scanner.nextInt();
        int k = scanner.nextInt();

        // 存储序列的数组
        int[] vec = new int[n];
        // 读取序列元素
        for (int i = 0; i < n; i++) {
            vec[i] = scanner.nextInt();
        }

        // 用于记录好序列的数量
        int num = 0;

        // 遍历所有可能的连续子序列
        for (int i = 0; i < n; i++) {
            for (int j = i + 1; j < n; j++) {
                // 先计算当前子序列的总和
                int sum = 0;
                for (int z = i; z <= j; z++) {
                    sum += vec[z];
                }

                // 遍历子序列中的每个元素,判断是否满足好序列的条件
                for (int z = i; z <= j; z++) {
                    // 减去当前元素的值,得到除该元素外其他元素的总和
                    sum -= vec[z];
                    if (sum % k == vec[z]) {
                        num++;
                        break;
                    }
                    // 把减去的元素值加回去,恢复总和
                    sum += vec[z];
                }
            }
        }

        // 输出好序列的数量
        System.out.println(num);
    }
}

C++版

cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;
// 本题的坑,真是大大的
// 注意!题目上说了,连续的子序列!切记是序列中衍生出来的子序列。
// 具体结合 '样例说明',l代表起始位置,r代表结束位置
// 切记!本题最多能套3层for循环,更多则必会超时
int main()
{
    int n,k;
    cin>>n>>k;
    vector<int> vec(n,0);
    for(int i=0; i<n; ++i) cin>>vec[i];
    int num = 0;
    
    for(int i=0; i<n; ++i){
        for(int j=i+1; j<n; ++j){ // 不存在序列数为1的情况
            // 以上两行的作用是,遍历所有子序列
            int sum = 0;
            for(int z=i; z<=j; ++z) sum+=vec[z]; // 先计算,本数列总和
            for(int z=i; z<=j; ++z){
                sum -= vec[z]; // 从z到i,注意判断那个数
                if(sum%k==vec[z]){
                    num++;
                    break;
                }
                sum += vec[z]; // 前方减去了,这里加回去
            }

        }
    }
    cout<<num<<endl;
    return 0;
}

3、小浩的国际象棋

问题描述

小浩有一个大小为 N×NN×N 的国际象棋棋盘。

有 NN 个主教以之字形的形式放置在棋盘的矩阵上,坐标分别为 (1,1),(2,2),(1,3),(2,4),(1,5),...​(1,1),(2,2),(1,3),(2,4),(1,5),...​。

例如对于 N=4​N=4​,棋盘初始时为:

已知主教只能斜向移动且每次可以移动任意距离。

你的任务是找到最少的移动次数,满足对于所有 1≤i≤N1≤i≤N,棋盘矩阵上的格子 (i,i)​(i,i)​ 都被主教占领了。

对于 N=4​N=4​,最后的位置应该为:

输入格式

第一行输入一个正整数 TT 表示测试数据的组数。

接下来 TT 行每行输入一个正整数 NN 表示棋盘的大小。

输出格式

对于每组测试数据,输出一个整数表示满足题目要求所需要的最少的移动次数,并换行。

样例输入1

4
1
4
2
6

样例输出1

0
3
0
6

说明

样例 11:棋盘的主教一开始已经处于最终位置了。

样例 22: 我们最少需要 33 次移动:

  • 将主教从 (2,2)(2,2) 移动到 (4,4)(4,4)。
  • 将主教从 (1,3)(1,3) 移动到 (2,2)(2,2)。
  • 将主教从 (2,4)(2,4) 移动到 (3,3)(3,3)。

样例 3​3​:棋盘的主教一开始已经处于最终位置了。

评测数据规模

对于所有的评测数据,1≤T≤2×1051≤T≤2×105,1≤N≤1091≤N≤109。

Java版

java 复制代码
import java.util.Scanner;
// 如果真要说,这其实就是一道观察题,首先分析 "说明"
// 然后在纸上画出,4个方格的、5个方格的、6个方格的,最终找出规律。
// 一般 思维题,就是找规律题
public class Main {
    public static void main(String[] args) {
        // 创建一个 Scanner 对象,用于从标准输入读取数据
        Scanner scanner = new Scanner(System.in);
        // 读取一个整数 t,表示测试用例的数量
        int t = scanner.nextInt();
        // 使用 while 循环处理 t 个测试用例
        while (t > 0) {
            // 读取每个测试用例中的整数 l,表示方格的数量
            int l = scanner.nextInt();
            // 如果方格数量 l 小于等于 2
            if (l <= 2) {
                // 输出 0 并换行
                System.out.println(0);
            } 
            // 如果方格数量 l 等于 3
            else if (l == 3) {
                // 输出 2 并换行
                System.out.println(2);
            } 
            // 如果方格数量 l 是偶数
            else if (l % 2 == 0) {
                // 根据规律计算结果并输出,结果为 2 加上 (l - 3) 除以 2 再乘以 3 最后加 1
                System.out.println(2 + ((l - 3) / 2) * 3 + 1);
            } 
            // 如果方格数量 l 是奇数
            else {
                // 根据规律计算结果并输出,结果为 2 加上 (l - 3) 除以 2 再乘以 3
                System.out.println(2 + ((l - 3) / 2) * 3);
            }
            // 测试用例数量减 1
            t--;
        }
        // 关闭 Scanner 对象,释放资源
        scanner.close();
    }
}

C++版

cpp 复制代码
#include <iostream>
using namespace std;
// 如果真要说,这其实就是一道观察题,首先分析 "说明"
// 然后在纸上画出,4个方格的、5个方格的、6个方格的,最终找出规律。
// 一般思维题,就是找规律题
int main()
{
    int t;
    cin>>t;
    while(t--){
        int l;
        cin>>l;
        if(l<=2) cout<<0<<endl;
        else if(l==3) cout<<2<<endl;
        else if(l%2==0) cout<<2+(l-3)/2*3+1<<endl;
        else cout<<2+(l-3)/2*3<<endl;
    }
    return 0;
}

4、缩位求和(蓝桥真题)

题目描述

在电子计算机普及以前,人们经常用一个粗略的方法来验算四则运算是否正确。

比如:248×15=3720248×15=3720

把乘数和被乘数分别逐位求和,如果是多位数再逐位求和,直到是 1 位数,得

2+4+8=14==>1+4=52+4+8=14==>1+4=5;

1+5=61+5=6;

5×65×6

而结果逐位求和为 3。

5×65×6 的结果逐位求和与 3 符合,说明正确的可能性很大!!(不能排除错误)

请你写一个计算机程序,对给定的字符串逐位求和。

输入描述

输入为一个由数字组成的串,表示 n (n<1000)n (n<1000) 位数;

输出描述

输出为一位数,表示反复逐位求和的结果。

输入输出样例

示例
输入

35379

输出

9

运行限制

  • 最大运行时间:1s
  • 最大运行内存: 256M

Java版

java 复制代码
import java.util.Scanner;
// 这道题,挺简单的
/*
  但是简单归简单,常人却往往会犯一个致命的错误
  在输入时,直接把数据放到一个 long 或 int 类型中,
  包错的!
  "n (n<1000) 位数"!1000位数,这得多大? 10的一千次方。
  所以,一般都先用string类型储存。
*/
public class Main {
    public static void main(String[] args) {
        // 创建一个 Scanner 对象,用于从标准输入读取数据
        Scanner scanner = new Scanner(System.in);
        // 声明一个 String 类型的变量 str,用于存储输入的字符串
        String str;
        // 从标准输入读取一行字符串并赋值给 str
        str = scanner.nextLine();
        // 声明一个 long 类型的变量 num,用于存储每一轮各位数字之和,初始化为 0
        long num = 0;

        // 当字符串 str 的长度大于 1 时,继续循环
        while (str.length() > 1) {
            // 遍历字符串 str 中的每一个字符
            for (int i = 0; i < str.length(); ++i) {
                // 将字符转换为对应的数字,并累加到 num 中
                num += str.charAt(i) - '0';
            }
            // 将 num 转换为字符串,并赋值给 str
            str = String.valueOf(num);
            // 将 num 重置为 0,以便下一轮计算
            num = 0;
        }
        // 输出最终结果
        System.out.println(str);
        // 关闭 Scanner 对象,释放资源
        scanner.close();
    }
}

C++版

java 复制代码
#include <iostream>
#define ll long long
using namespace std;
// 这道题,挺简单的
/*
  但是简单归简单,常人却往往会犯一个致命的错误
  在输入时,直接把数据放到一个 long 或 int 类型中,
  包错的!
  "n (n<1000) 位数"!1000位数,这得多大? 10的一千次方。
  所以,一般都先用string类型储存。
*/

int main()
{
    string str;
    cin>>str;
    ll num=0;
    while(str.size()>1){
        for(int i=0; i<str.size(); ++i){
            num+=str[i]-'0';
        }
        str=to_string(num);
        num=0;
    }
    cout<<str<<endl;
    return 0;
}

5、Goshopping

问题描述

最近 Awell 的运气特别好,这不,他在路边摊买彩票,居然中了大奖。秉着见者有份的原则,他准备请咱们学校 ACM-ICPC 训练基地的全体队员逛商场。

赶巧交大旁边有一家商场新店开张,正在进行打折促销活动。于是,咱们所有队员都在商场中大肆购买之后,在收银台前排起了长队。

话说回来,这家商场的打折方式有些奇怪:他们从在收银台前付账的所有 nn 位顾客中,所有的第 mm 的倍数名顾客享受七五折优惠,其余顾客只能享受九五折。为了方便付账,Awell 拜托老板将付账者的姓名和付款金额打印出来,作为参考。   你需要注意的是,在收银台前长长的队伍中,有的可不止是 ACM 队员,同样,还有很多交大的同学慕名前来消费,为了区分他们,我们规定,所有 ACM 队员必须在姓名前加上前缀 ACM。

现在,请机智的你为 Awell 编写一个小程序,算一算他总共需要花费多少钱呢?

输入格式

输入数据包含不超过 55 组,每组第一行有两个整数 n,m(1≤n,m≤1000)n,m(1≤n,m≤1000),分别代表着在收银台前队伍的全部人数,以及商家将会选择每第 mm 位顾客打 7.57.5 折。

接下来有 nn 行,每行将会输入消费者的姓名(长度不超过 2020 个字符),以及他们各自消费的金额(消费金额不超过 10001000)。

输出格式

每组数据输出一行,每行一个实数,表示 Awell 总共需要花费多少。

你应该注意的是,老板只收取"角"作为最小单位,而且他是一个锱铢必较的人,所以,如果你所付金额中存在小于 0.10.1 元的部分,那就至少要付 0.10.1 元给他(想着即将消瘦的钱包,Awell 泪目中)。

输入样例

4 2
Newee 123.12
ACMAwell 100
PRO 345.5
Sirius 456.99
5 2
Newee 123.12
ACMAwell 100
PROPHET 345.5
Sirius 456.99
ACMProphetK 100

输出样例

75.0
170.0

Java版

java 复制代码
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        // 创建一个 Scanner 对象,用于从标准输入读取数据
        Scanner scanner = new Scanner(System.in);
        // 持续读取输入,直到没有更多输入为止
        while (scanner.hasNext()) {
            // 读取第一个整数,表示人数
            int n = scanner.nextInt();
            // 读取第二个整数,表示第 m 位顾客
            int m = scanner.nextInt();
            // 用于存储顾客的姓名
            String str;
            // 用于存储所有符合条件的顾客的总金额
            double all_money = 0;
            // 用于存储单个顾客的金额
            double money;
            // 循环 n 次,处理每位顾客的信息
            for (int i = 0; i < n; ++i) {
                // 读取顾客的姓名
                str = scanner.next();
                // 读取顾客的金额
                money = scanner.nextDouble();
                // 在顾客姓名后面追加三个 0
                str = str + "000";
                // 截取姓名的前三个字符
                str = str.substring(0, 3);
                // 判断当前顾客是否是第 m 位顾客且姓名前三位是 "ACM"
                if ((i + 1) % m == 0 && str.equals("ACM")) {
                    // 如果是,按照 75% 的折扣计算金额并累加到总金额中
                    all_money += money * 0.75;
                } 
                // 判断姓名前三位是否是 "ACM"
                else if (str.equals("ACM")) {
                    // 如果是,按照 95% 的折扣计算金额并累加到总金额中
                    all_money += money * 0.95;
                }
            }

            // 实现四舍五入并保留一位小数,然后输出结果,同时换行
            System.out.printf("%.1f\n", all_money + 0.05);
        }
        // 关闭 Scanner 对象,释放资源
        scanner.close();
    }
}

C++版

cpp 复制代码
#include <iostream>
using namespace std;
// 本题两大坑
// 1、输出函数的合理运用
// 2、换行符合的运用
int main()
{
    int n,m; // n(人数) m(第m位顾客)

    while(cin>>n>>m){
        string str;
        double all_money = 0;
        double money;
        for(int i=0; i<n; ++i){
            cin>>str>>money;
            str += "000";
            str = str.substr(0,3);
            if((i+1)%m==0 && str=="ACM"){
                all_money += money*0.75;
            }else if(str=="ACM") {
                all_money += money * 0.95;
            }
        }
        printf("%.1f\n",all_money+0.05); // 这里合理的结合了题目,与四舍五入的特性。
    }
    return 0;
}

基础:

6、交换瓶子(蓝桥真题)

题目描述

有 NN 个瓶子,编号 1 ~ NN,放在架子上。

比如有 5 个瓶子:

2 1 3 5 4

要求每次拿起 2 个瓶子,交换它们的位置。

经过若干次后,使得瓶子的序号为:

1 2 3 4 5

对于这么简单的情况,显然,至少需要交换 2 次就可以复位。

如果瓶子更多呢?你可以通过编程来解决。

输入描述

输入格式为两行:

第一行: 一个正整数 N (N<104)N (N<104), 表示瓶子的数目

第二行: NN 个正整数,用空格分开,表示瓶子目前的排列情况。

输出描述

输出数据为一行一个正整数,表示至少交换多少次,才能完成排序。

输入输出样例

示例
输入

cpp 复制代码
5
3 1 2 5 4

输出

3

Java版

java 复制代码
import java.util.Scanner;
import java.util.ArrayList;
/*
  第一次看到这道题目的时候,我陷入了沉思...什么鬼
  经过观察,忽然发现,这道题目不就是 排序吗???
  把无序的换成有序的(1,2,3,4...),哈哈,可真是个大聪明!
  其实这道题有点像冒泡排序,但本质上却又相差甚远
*/
/*
  上一个模块只是拓展思维用的,没啥可信性。
  本题最好的解法,其实是对号入座。
  确保每次交换,一定有一个能回归原位,具体看代码
  *这种该死的,对代码的控制力度*
*/
public class Main {
    public static void main(String[] args) {
        // 创建 Scanner 对象,用于读取输入
        Scanner scanner = new Scanner(System.in);
        // 读取瓶子的数量
        int n = scanner.nextInt();
        // 创建一个数组,用于存储瓶子的编号
        int[] vec = new int[n + 1];
        for (int i = 1; i <= n; ++i) {
            vec[i] = scanner.nextInt();
        }
        // 初始化交换次数
        int ans = 0;
        // 遍历每个瓶子
        for (int i = 1; i <= n; ++i) {
            int j = i;
            // 如果当前瓶子的编号不是 i
            if (vec[j] != i) {
                // 找到编号为 i 的瓶子的位置
                while (vec[j] != i) {
                    j++;
                }
                // 交换两个瓶子的位置
                int temp = vec[j];
                vec[j] = vec[i];
                vec[i] = temp;
                // 增加交换次数
                ans++;
            }
        }
        // 输出交换次数
        System.out.println(ans);
        // 关闭 Scanner 对象
        scanner.close();
    }
}

C++版

cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;
/*
  第一次看到这道题目的时候,我陷入了沉思...什么鬼
  经过观察,忽然发现,这道题目不就是 排序吗???
  把无序的换成有序的(1,2,3,4...),哈哈,可真是个大聪明!
  其实这道题有点像冒泡排序,但本质上却又相差甚远
*/
/*
  上一个模块只是拓展思维用的,没啥可信性。
  本题最好的解法,其实是对号入座。
  确保每次交换,一定有一个能回归原位,具体看代码
  *这种该死的,对代码的控制力度*
*/
int main()
{
    int n;
    cin>>n;
    vector<int> vec(n+1,0);
    for(int i=1; i<=n; ++i) cin>>vec[i];
    int ans = 0;
    for(int i=1; i<=n; ++i) {
        int j=i;
        if(vec[j]!=i){
            while(vec[j]!=i){
                j++;
            }
            vec[j]=vec[i];
            vec[i]=i;
            ans++;
        }
    }
    cout<<ans<<endl;
    return 0;
}

7、奇怪的数列(蓝桥真题)

第一次看到这题,有点惊讶,这不又是一道,过程模拟吗!?

而且是过程模拟中一道 非常适合打基础 的题目

** 这该死的对代码的控制力度。

题目描述

从 X 星截获一份电码,是一些数字,如下:

13

1113

3113

132113

1113122113

⋯⋯

YY 博士经彻夜研究,发现了规律:

第一行的数字随便是什么,以后每一行都是对上一行"读出来"

比如第 2 行,是对第 1 行的描述,意思是:1 个 1,1 个 3,所以是:1113

第 3 行,意思是:3 个 1,1 个 3,所以是:3113

请你编写一个程序,可以从初始数字开始,连续进行这样的变换。

输入描述

第一行输入一个数字组成的串,不超过 100 位。

第二行,一个数字 nn,表示需要你连续变换多少次,nn 不超过 20。

输出描述

输出一个串,表示最后一次变换完的结果。

输入输出样例

示例
输入

5
7

输出

13211321322115

Java版

java 复制代码
import java.util.Scanner;
public class Main {
    public static void main(String[] args) {
        // 创建一个 Scanner 对象,用于从标准输入读取数据
        Scanner scanner = new Scanner(System.in);
        // 读取原字符串
        String str1 = scanner.next();
        // 读取操作次数
        int t = scanner.nextInt();
        // 关闭 Scanner 对象,避免资源泄漏
        scanner.close();
        // 循环 t 次,进行字符串变换操作
        while (t-- > 0) {
            // 初始化计数器,用于记录连续相同字符的数量
            int num = 1;
            // 初始化当前字符为原字符串的第一个字符
            char c = str1.charAt(0);
            // 用于存储变换后的字符串
            StringBuilder str2 = new StringBuilder();
            // 遍历原字符串,从第二个字符开始
            for (int i = 1; i < str1.length(); i++) {
                // 如果当前字符与前一个字符不同
                if (c != str1.charAt(i)) {
                    // 将连续相同字符的数量和该字符添加到 str2 中
                    str2.append(num).append(c);
                    // 重置计数器为 1
                    num = 1;
                    // 更新当前字符为新的字符
                    c = str1.charAt(i);
                } else {
                    // 如果当前字符与前一个字符相同,计数器加 1
                    num++;
                }
            }
            // 处理字符串的最后一组连续相同字符
            str2.append(num).append(c);
            // 将变换后的字符串赋值给 str1
            str1 = str2.toString();
        }
        // 输出最终变换后的字符串
        System.out.println(str1);
    }
}

C++版

cpp 复制代码
#include <iostream>
using namespace std;
/*
  第一次看到这题,有点惊讶,这不又是一道,过程模拟吗!?
  而且是过程模拟中一道 非常适合打基础 的题目。
*/
/*
  这该死的对代码的控制力度
*/
int main()
{
    int t;
    string str1; // 原字符串
    string str2=""; // 接受更换的字符串
    cin>>str1>>t;

    while(t--){
        int num = 1;
        char c = str1[0];
        for(int i=1; i<str1.size(); ++i){
            if(c!=str1[i]){ // 关键点处
                str2 += to_string(num) + c;
                num = 1;
                c=str1[i];
            }else{
                num++;
            }
        }
        str2 += to_string(num) + c;
        str1 = str2;
        str2 = "";
    }
    cout<<str1<<endl;
    return 0;
}

8、长草(蓝桥真题)

与其说本题是一道模拟题,倒不如说本题是一道考察深搜的bfs题目。

思考良久,感觉挺遗憾的,本题没有用正宗的bfs(递归)方法写出来。

网上找了好多答案,都是七七八八,甚至有用队列的。

那本题,就当作是一个学习bfs过渡题吧。

题目描述

小明有一块空地,他将这块空地划分为 nn 行 mm 列的小块,每行和每列的长度都为 1。

小明选了其中的一些小块空地,种上了草,其他小块仍然保持是空地。

这些草长得很快,每个月,草都会向外长出一些,如果一个小块种了草,则它将向自己的上、下、左、右四小块空地扩展,

这四小块空地都将变为有草的小块。请告诉小明,kk 个月后空地上哪些地方有草。

输入描述

输入的第一行包含两个整数 n,mn,m。

接下来 nn 行,每行包含 mm 个字母,表示初始的空地状态,字母之间没有空格。如果为小数点,表示为空地,如果字母为 gg,表示种了草。

接下来包含一个整数 kk。 其中,2≤n,m≤1000,1≤k≤10002≤n,m≤1000,1≤k≤1000。

输出描述

输出 nn 行,每行包含 mm 个字母,表示 kk 个月后空地的状态。如果为小数点,表示为空地,如果字母为 gg,表示长了草。

输入输出样例

示例
输入

4 5
.g...
.....
..g..
.....
2

输出

gggg.
gggg.
ggggg
.ggg.

运行限制

  • 最大运行时间:1s
  • 最大运行内存: 256M

Java版

java 复制代码
import java.util.Scanner;
// 优化技巧,预处理打表 -> 提前开辟数组
// 定义全局变量 a 和 b,用于存储草地状态
public class GrassGrowthSimulation {
    // 全局变量 a,用于存储草地的当前状态
    static int[][] a = new int[1005][1005];
    // 全局变量 b,用于复刻 a 的状态,辅助更新草地状态
    static int[][] b = new int[1005][1005];

    public static void main(String[] args) {
        // 创建 Scanner 对象,用于从标准输入读取数据
        Scanner scanner = new Scanner(System.in);

        // 读取草地的行数 n 和列数 m
        int n = scanner.nextInt();
        int m = scanner.nextInt();

        // 消耗掉 nextInt() 后的换行符
        scanner.nextLine();

        // 读取草地的初始状态
        for (int i = 0; i < n; i++) {
            // 读取一行字符串,表示当前行的草地状态
            String str = scanner.nextLine();
            for (int j = 0; j < m; j++) {
                // 如果字符为 '.',表示没有草,将 a[i][j] 设为 0
                if (str.charAt(j) == '.') a[i][j] = 0;
                // 否则表示有草,将 a[i][j] 设为 1
                else a[i][j] = 1;
            }
        }
        // 读取需要模拟的天数 k
        int k = scanner.nextInt();
        // 关闭 Scanner 对象,防止资源泄漏
        scanner.close();
        // 循环 k 次,模拟每天草地的扩展
        while (k-- > 0) {
            // 调用 dfs 方法进行草地状态的更新
            dfs(n, m);
        }
        // 输出最终的草地状态
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                // 如果 a[i][j] 为 0,表示没有草,输出 '.'
                if (a[i][j] == 0) System.out.print(".");
                // 否则表示有草,输出 'g'
                else System.out.print("g");
            }
            // 换行,输出下一行的草地状态
            System.out.println();
        }
    }
    // dfs 方法用于更新草地的状态
    public static void dfs(int n, int m) {
        // 将 a 数组的值复制到 b 数组中,防止在更新 a 数组时产生不必要的影响
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                b[i][j] = a[i][j];
            }
        }
        // 遍历 b 数组,根据 b 数组的值更新 a 数组
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                // 如果 b[i][j] 为 1,表示该位置有草
                if (b[i][j] == 1) {
                    // 检查上方位置,如果存在且没有草,则将其设为有草
                    if (i>=1) a[i - 1][j] = 1;
                    // 检查下方位置,如果存在且没有草,则将其设为有草
                    if (i<n-1) a[i + 1][j] = 1;
                    // 检查左方位置,如果存在且没有草,则将其设为有草
                    if (j>=1) a[i][j - 1] = 1;
                    // 检查右方位置,如果存在且没有草,则将其设为有草
                    if (j<m-1) a[i][j + 1] = 1;
                }
            }
        }
    }
}
/*
  拓展: 像本题打表时
  起初我用的是 a[10005][10005],b[10005][10005]
  而 a 和 b 数组占用的空间非常大(10001 * 10001 * 4 * 2 字节,约 760MB)
  本题给出的是:
  最大运行时间:1s
  最大运行内存: 256M
  所以合适最好。
 */

C++版

cpp 复制代码
#include <iostream>
using namespace std;
/*
  与其说本题是一道模拟题,倒不如说本题是一道考察深搜的bfs题目
*/
/*
  思考良久,感觉挺遗憾的,本题没有用正宗的bfs(递归)方法写出来
  网上找了好多答案,都是七七八八,甚至有用队列的
  那本题,就当作是一个学习bfs过渡题吧
*/
/*
  拓展: 像本题打表时
  起初我用的是 a[10005][10005],b[10005][10005]
  而 a 和 b 数组占用的空间非常大(10001 * 10001 * 4 * 2 字节,约 760MB)
  本题给出的是:
  最大运行时间:1s
  最大运行内存: 256M
  所以合适最好。
 */
// 优化技巧,预处理打表 -> 提前开辟数组
int a[1005][1005],b[1005][1005]; // a是正确的储存,b用来复刻
void dfs(int n, int m){
    // 转移到b组,防止a组扩张,产生不必要的影响
    for(int i=0; i<n; ++i)
        for(int j = 0; j<m; ++j)
            b[i][j]=a[i][j];

    for(int i=0; i<n; ++i){
        for(int j=0; j<m; ++j){
            if(b[i][j]==1){ // 有草的前提下
                // 设置边界,i与j的边界都要不同
                if(i>=1) a[i-1][j]=1;
                if(i<n-1) a[i+1][j]=1;
                if(j>=1) a[i][j-1]=1;
                if(j<m-1) a[i][j+1]=1;
            }
        }
    }
}

int main()
{
    int n,m,k;
    cin>>n>>m;
    string str;
// 输入
    // 有草的地方设为1,没草的地方谁
    for(int i=0; i<n; ++i){
        cin>>str;
        for(int j = 0; j<m; ++j){
            if(str[j]=='.') a[i][j]=0;
            else a[i][j]=1;
        }
    }
    cin>>k; // 输入一共需要多少天
    while(k--){
        dfs(n,m); // 每天向外扩展一圈
    }
// 输出
    for(int i=0; i<n; ++i){
        for(int j = 0; j<m; ++j){
            if(a[i][j]==0) cout<<".";
            else cout<<"g";
        }
        cout<<endl;
    }
    return 0;
}

日期系列

9、跑步(蓝桥真题)

本题为填空题,只需要算出结果后,在代码中使用输出语句将所填结果输出即可。

问题描述

小蓝每周六、周日都晨跑,每月的 11、1111、2121、3131 日也晨跑。其它时间不晨跑。

已知 20222022 年 11 月 11 日是周六,请问小蓝整个 20222022 年晨跑多少天?

Java版

java 复制代码
public class Main {
    public static void main(String[] args) {
        // 初始化天数,初始值为 2
        int day = 2;
        // 用于标记经过的总天数
        int flag = 0;
        // 定义一个数组,存储每个月的天数,假设这一年是闰年,2 月有 29 天
        int[] monthDay = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

        // 外层循环遍历每个月
        for (int i = 0; i < monthDay.length; i++) {
            // 内层循环遍历当前月的每一天
            for (int j = 0; j < monthDay[i]; j++) {
                // 跳过 1 月的第 2 天和第 3 天的重复计算
                if (i == 0 && (j == 1 || j == 2)) {
                    continue;
                }
                // 经过的总天数加 1
                flag++;
                // 判断是否满足增加天数的条件
                if (flag % 6 == 0 || flag % 7 == 0 || j + 1 == 11 || j + 1 == 1 || j + 1 == 21 || j + 1 == 31) {
                    // 如果满足条件,总天数加 1
                    day++;
                }
            }
        }
        // 输出最终的天数
        System.out.println(day);
    }
}

C++版

cpp 复制代码
#include <iostream>
using namespace std;
int main()
{
  // 首先判断是否为闰年,不是闰年,2月有29天
  int day = 2;
  int flag = 0;
  // 本题主打合理运用数据进行计算
  int monthDay[] = {31,29,31,30,31,30,31,31,30,31,30,31};
  for(int i=0; i<sizeof(monthDay)/sizeof(monthDay[0]); ++i){ 
      for(int j=0; j<monthDay[i]; ++j){
        if(i==0&&(j==1||j==2)) continue; // 跳过重复计算
        flag++;
        if(flag%6==0 || flag%7==0 || j+1==11 || j+1==1|| j+1==21 || j+1==31) day++;
      }
  }
  cout<<day<<endl;

  return 0;
}

// 数组定义的三种格式
// 数据类型 数组名[长度]
// 数据类型 数组名[数组长度] = { 值1,值2 ... };
// 数据类型 数组名[] = { 值1,值2 ... };

10、跑步计划(蓝桥真题)

问题描述

小蓝计划在某天的日期中出现 11 时跑 55 千米,否则只跑 11 千米。注意日期中出现 11 不仅指年月日也指星期。

请问按照小蓝的计划,20232023 年小蓝总共会跑步锻炼多少千米?例如,55 月 11 日、11 月 1313 日、1111 月 55 日、44 月 33 日 (星期一) 小蓝会跑 55 千米,而 55 月 2323 日小蓝会跑 11 千米 (示例日期均为 20232023 年)

答案提交

这是一道结果填空的题,你只需要算出结果后提交即可。本题的结果为一个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。

Java版

java 复制代码
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        // 用于累加最终结果的变量,初始化为 0
        int s = 0;
        // 定义一个数组,存储平年每个月的天数
        int[] monthDay = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
        // 用于记录从年初开始累计的天数
        int flag = 0;

        // 外层循环,遍历一年中的每个月
        for (int i = 0; i < monthDay.length; i++) {
            // 内层循环,遍历当前月份的每一天
            for (int j = 0; j < monthDay[i]; j++) {
                // 每过一天,累计天数加 1
                flag++;

                // 判断是否满足月份相关的条件
                if (i + 1 == 1 || i + 1 >= 10) {
                    // 如果是 1 月或者 10 月及以后的月份,累加 5 到结果中
                    s += 5;
                } 
                // 判断是否满足日期相关的条件
                else if (j + 1 == 1 || (10 <= j + 1 && j + 1 <= 19) || j + 1 == 21 || j + 1 == 31) {
                    // 如果是 1 号、10 - 19 号、21 号或者 31 号,累加 5 到结果中
                    s += 5;
                } 
                // 判断是否满足星期相关的条件
                else if (flag % 7 == 2) {
                    // 如果当前累计天数对 7 取余结果为 2,累加 5 到结果中
                    s += 5;
                } 
                else {
                    // 如果不满足上述任何条件,累加 1 到结果中
                    s++;
                }
            }
        }

        // 输出最终的累加结果
        System.out.println(s);
    }
}

C++版

cpp 复制代码
#include <iostream>
using namespace std;
int main()
{
    int s = 0;
    int monthDay[] = {31,28,31,30,31,30,31,31,30,31,30,31}; // 每月日期
    // 需考虑 3 种情况
    // 1、月份
    // 2、日期
    // 3、星期 , 找准1月1,是几月几日非常重要
    int flag = 0;
    for(int i=0; i<sizeof(monthDay)/sizeof(monthDay[0]); ++i){
        for(int j=0; j<monthDay[i]; ++j){
            flag++;
            if(i+1==1 || i+1>=10){ // 月份
                s += 5;
            }else if(j+1==1 || (10<=j+1 && j+1<=19) || j+1 == 21 || j+1 == 31){ // 日
                s += 5;
            }else if(flag%7==2){ // 星期
                s += 5;
            }else{
                s++;
            }
        }
    }
    cout<<s<<endl;
    return 0;
}

11、定时任务(蓝桥真题)

问题描述

Cron 表达式在定时任务中经常被使用,在这里我们用了一种简化后的版本 SimpleCron 表达式:SimpleCron 表达式是一个具有时间含义的字符串,字符串以 44 个空格隔开,分为 55 个域,格式为 XXXXXXXXXX,其中 XX 是一个域的占位符。55 个域从左至右依次为秒 (0−59)(0−59)、分钟 (0−59)(0−59)、小时 (0−23)(0−23)、日期 (1−31)(1−31)、月份 (1−12)(1−12),其中括号内为他们各自的取值范围。同时域内取值也可以使用一些特殊字符(每个域内只能使用一种特殊字符):

  1. 特殊字符 ∗∗ (ASCII 码为 4242)表示所有可能的值。例如:在分钟域内表示每一分钟;在日期域内表示月内的每一天。
  2. 特殊字符 ,, (ASCII 码为 4444)表示列出枚举值。例如:在秒域内,3,203,20 表示分别在 33 秒和 2020 秒执行一次任务。
  3. 特殊字符 −− (ASCII 码为 4545)表示范围,可以视为连续的若干个枚举值。例如:1−51−5 等价于 1,2,3,4,51,2,3,4,5。

例如,421,3,151−31∗421,3,151−31∗表示的含义是每个月份中的每一天中的 01:02:0401:02:04、03:02:0403:02:04、15:02:0415:02:04 这三个时刻各执行一次,在 20232023 年一共会执行 10951095 次。

现在给出你一个合法的 SimpleCron 表达式,其中用到的所有数字均没有前导零。请问在 20232023 一整年当中,使用了这个表达式的定时任务总计会执行多少次?

输入格式

输入一行,包含一个 SimpleCron 字符串。

输出格式

输出一行,包含一个整数表示答案。

样例输入

4 2 1,3,15 1-31 *

样例输出

1095

Java版

java 复制代码
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

public class Main {
    // 生成数量的方法,根据输入的字符串和范围生成对应的数字列表
    public static List<Integer> generateNum(String str, int range) {
        List<Integer> res = new ArrayList<>();
        int cur = 0;
        int lis = -1; // 作用于 - 时

        // 遍历输入字符串中的每个字符
        for (char c : str.toCharArray()) {
            if (c == ',') {
                // 遇到逗号,将当前累计的数字添加到结果列表中,并重置当前数字
                res.add(cur);
                cur = 0;
            } else if (c == '-') {
                // 遇到减号,记录当前数字作为范围起始值,并重置当前数字
                lis = cur;
                cur = 0;
            } else if (c == '*') {
                // 遇到星号,将 1 到 range 的所有数字添加到结果列表中
                for (int i = 0; i < range; i++) {
                    res.add(i + 1);
                }
                return res;
            } else {
                // 其他情况,将字符转换为数字并累加到当前数字中
                cur = cur * 10 + (c - '0');
            }
        }

        // 处理范围情况
        if (lis != -1) {
            for (int i = lis; i <= Math.min(range, cur); i++) {
                res.add(i);
            }
        } else {
            // 没有范围,直接添加当前数字
            res.add(cur);
        }
        return res;
    }

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        // 每个月份对应的天数
        int[] monthDay = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

        // 输入秒、分、时、日、月的配置信息
        String S = scanner.next();
        String M = scanner.next();
        String H = scanner.next();
        String D = scanner.next();
        String MON = scanner.next();

        // 计算秒、分、时的可能组合数量
        int num1 = generateNum(S, 60).size() * generateNum(M, 60).size() * generateNum(H, 24).size();

        // 计算最终的可能组合数量
        int num2 = 0;
        // 遍历每个可能的月份
        for (int i : generateNum(MON, 12)) {
            // 每个 i 都是月份
            num2 += generateNum(D, monthDay[i - 1]).size() * num1;
        }

        // 输出结果
        System.out.println(num2);
        scanner.close();
    }
}

C++版

cpp 复制代码
#include "bits/stdc++.h"
using namespace std;

// 我很佩服这道题,缘由是因为,它通过vector巧妙地代替了数字,合理的搭配、vector的巧妙运用与for-range的运用,让人感觉道赏心悦目

vector<int> generateNum(string str, int range){ // 生成数量
    vector<int> res;
    // (每个域内只能使用一种特殊字符)
    int cur=0;
    int lis = -1; // 作用于-时,
    for(char c : str){
        if(c==','){
            res.push_back(cur);
            cur = 0;
        }else if(c=='-'){
            lis = cur;
            cur = 0;
        }else if(c=='*'){
            for(int i=0; i<range; i++) res.push_back(i+1);
            return res;
        }else{ // 这个用来计算时间
            cur = cur*10 + c-'0';
        }
    }
    // 存入
    if(lis!=-1){
        for(int i=lis; i<=min(range,cur); ++i) res.push_back(i);
    }else{
        res.push_back(cur);
    }
    return res;
}

int main()
{
    int monthDay[] = {31,28,31,30,31,30,31,31,30,31,30,31}; // 每个月份,对应的天数

    // 用集合代替,次数,简直是一个天才
    string S,M,H,D,MON; cin>>S>>M>>H>>D>>MON; // 输入所有

    // 计算秒、分、时的可能
    int num1 = generateNum(S,60).size()*generateNum(M,60).size()*generateNum(H,24).size();

    // 对月份也是有要求的
    int num2 = 0;
    for(int i : generateNum(MON,12)){
        // 每个i都是月份
        num2+= generateNum(D,monthDay[i-1]).size()*num1;
    }
    cout<<num2<<endl;

    return 0;
}

进阶:

12、赢球票 (蓝桥真题)

题目描述

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

主持人拿出 NN 张卡片(上面写着 1⋯N1⋯N 的数字),打乱顺序,排成一个圆圈。

你可以从任意一张卡片开始顺时针数数: 1,2,3 ⋯⋯

如果数到的数字刚好和卡片上的数字相同,则把该卡片收入囊中,从下一个卡片重新数数。

直到再无法收获任何卡片,游戏结束。囊中卡片数字的和就是赢得球票的张数。

比如:

卡片排列是:1 2 3

我们从 1 号卡开始数,就把 1 号卡拿走。再从 2 号卡开始,但数的数字无法与卡片对上,很快数字越来越大,不可能再拿走卡片了。因此这次我们只赢得了 1 张球票。

还不算太坏!如果我们开始就傻傻地从 2 或 3 号卡片数起,那就一张卡片都拿不到了。

如果运气好,卡片排列是 2 1 3,那我们可以顺利拿到所有的卡片!

本题的目标:已知顺时针卡片序列,随便你从哪里开始数,求最多能赢多少张球票(就是收入囊中的卡片数字之和)

输入描述

第一行一个整数 NN (N≤100N≤100),表示卡片数目。

第二行 NN 个整数,表示顺时针排列的卡片。

输出描述

输出一行,一个整数,表示最好情况下能赢得多少张球票。

输入输出样例

示例
输入

3
1 2 3

输出

1

Java版

java 复制代码
import java.util.Scanner;
import java.util.Arrays;
/*
  简单列一下思路
  1、先列一个循环将所有可能循环一遍
  2、然后标记一个数,判断最大值是否已经大于纸牌最大值,否则结束
  3、拿走某个牌时,可用数据记忆是否遍历过这个。
*/
public class CardGame {
    public static void main(String[] args) {
        // 创建 Scanner 对象用于从标准输入读取数据
        Scanner scanner = new Scanner(System.in);
        // 读取纸牌的数量 n
        int n = scanner.nextInt();
        // 创建一个长度为 n 的整型数组来存储每张纸牌的值
        int[] vec = new int[n];
        // 循环读取 n 张纸牌的值并存储到数组 vec 中
        for (int i = 0; i < n; i++) {
            vec[i] = scanner.nextInt();
        }
        // 用于记录最大的得分,初始化为 0
        int flag = 0;
        // 从每个位置开始进行一次完整的尝试
        for (int i = 0; i < n; i++) {
            // 创建一个长度为 n 的布尔型数组,用于标记每张纸牌是否已被使用
            // 初始值都为 false,表示所有纸牌都未被使用
            boolean[] used = new boolean[n];
            // 记录当前已经消除的纸牌数量,用于判断是否所有纸牌都已消除
            int cur = 0;
            // 当前处理的纸牌的索引,从位置 i 开始
            int index = i;
            // 当前要匹配的纸牌张数,初始为 1
            int count = 1;
            // 当前获得的得分,初始为 0
            int num = 0;
            // 开始一个无限循环,模拟依次检查纸牌的过程
            while (true) {
                // 如果当前位置的纸牌还未被使用
                if (!used[index]) {
                    // 检查当前纸牌的值是否等于要匹配的纸牌张数
                    if (vec[index] == count) {
                        // 若相等,说明可以消除这张纸牌
                        cur++;  // 消除的纸牌数量加 1
                        num += count;  // 得分加上当前匹配的纸牌张数
                        used[index] = true;  // 标记这张纸牌已被使用
                        count = 1;  // 重置要匹配的纸牌张数为 1
                    } else {
                        // 若不相等,要匹配的纸牌张数加 1
                        count++;
                    }
                }
                // 更新索引,通过取模运算确保索引在 0 到 n - 1 的范围内循环
                index = (index + 1) % n;
                // 判断是否满足结束条件:要匹配的纸牌张数大于 n 或者所有纸牌都已消除
                if (count > n || cur == n) {
                    break;  // 满足条件则跳出循环
                }
            }
            // 更新最大得分,取当前尝试的得分和之前记录的最大得分中的较大值
            flag = Math.max(num, flag);
        }
        // 输出最大得分
        System.out.println(flag);
        // 关闭 Scanner 对象,释放资源
        scanner.close();
    }
}

C++版

cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;
/*
  其实不是我说,要是我就直接暴力求解了
*/
/*
  让我简单列一下思路
  1、先列一个循环将所有可能循环一遍
  2、然后标记一个数,判断最大值是否已经大于纸牌最大值,否则结束
  3、拿走某个牌时,可用数据记忆是否遍历过这个。
*/
int main()
{
    int n;
    cin>>n;
    vector<int> vec(n,0);

    for(int i=0; i<n; ++i) cin>>vec[i];
    int flag=0;
    for(int i=0; i<n; ++i){ // 以此从每个地点开始一次
        vector<bool> used(n, false); // 为false表示,表示未用过
        int cur = 0; // 消除的卡牌个数,本题用于判断结束条件
        int index = i; // 索引,
        int count = 1; // 卡牌张数
        int num = 0; // 当前的求票总和

        while(1){ // 开始循环
            if(!used[index]) { // 改位置的卡牌还存在
                if (vec[index] == count) { // 确保能匹配那个数据
                    cur++;
                    num+=count;
                    used[index] = true;
                    count = 1;
                } else { // 不同时
                    count++;
                }
            }
            index = (index+1)%n; // 维持正常循环
            if(count>n||cur==n) break; // 对!结束的不应该这么草率
        }
        // 这里存储一下
        flag = max(num,flag);
    }
    cout<<flag<<endl;
    return 0;
}

13、神奇闹钟(蓝桥真题)

问题描述

小蓝发现了一个神奇的闹钟,从纪元时间(19701970 年 11 月 11 日 00:00:0000:00:00)开始,每经过 xx 分钟,这个闹钟便会触发一次闹铃 (纪元时间也会响铃)。这引起了小蓝的兴趣,他想要好好研究下这个闹钟。

对于给出的任意一个格式为 уууу-MM-ddHH:mm:ssуууу-MM-ddHH:mm:ss 的时间,小蓝想要知道在这个时间点之前 (包含这个时间点) 的最近的一次闹铃时间是哪个时间?

注意,你不必考虑时区问题。

输入格式

输入的第一行包含一个整数 TT,表示每次输入包含 TT 组数据。

接下来依次描述 TT 组数据。

每组数据一行,包含一个时间(格式为 уууу-MM-ddHH:mm:ssуууу-MM-ddHH:mm:ss)和一个整数 xx,其中 xx 表示闹铃时间间隔(单位为分钟)。

输出格式

输出 TT 行,每行包含一个时间(格式为 уууу-MM-ddHH:mm:ssуууу-MM-ddHH:mm:ss),依次表示每组数据的答案。

样例输入

2
2016-09-07 18:24:33 10
2037-01-05 01:40:43 30

样例输出

2016-09-07 18:20:00
2037-01-05 01:30:00

评测用例规模与约定

对于所有评测用例,1≤T≤10,1≤x≤10001≤T≤10,1≤x≤1000,保证所有的时间格式都是合法的。

Java版

cpp 复制代码
import java.util.Scanner;

// 在这里,我先声明一下,答案上能套更简单的模版,但意义不大
// 但如果实在想用模版,都行,这里更多的细节与思路


public class TimeCalculation {

    // 计算从 1970 年 1 月 1 日到指定日期和时间的总分钟数
    public static long generate(int year, int month, int day, int hour, int minute, int second, int sub) {
        int totalDays = 0;

        // 计算从 1970 年到指定年份前一年的总天数
        for (int y = 1970; y < year; y++) {
            if (isLeapYear(y)) {
                totalDays += 366;
            } else {
                totalDays += 365;
            }
        }

        // 计算到指定月份前一个月的总天数
        for (int m = 1; m < month; m++) {
            totalDays += getDaysInMonth(m, year);
        }

        // 加上指定日期的天数(减去 1 是因为从 1 号开始算)
        totalDays += (day - 1);

        // 计算总分钟数
        return (long) totalDays * 24 * 60 + hour * 60 + minute;
    }

    // 判断是否为闰年
    public static boolean isLeapYear(int year) {
        return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
    }

    // 获取指定月份的天数
    public static int getDaysInMonth(int month, int year) {
        switch (month) {
            case 1:
            case 3:
            case 5:
            case 7:
            case 8:
            case 10:
            case 12:
                return 31;
            case 4:
            case 6:
            case 9:
            case 11:
                return 30;
            case 2:
                return isLeapYear(year) ? 29 : 28;
            default:
                return 0;
        }
    }

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int t = scanner.nextInt(); // 读取测试用例的数量

        for (int i = 0; i < t; i++) {
            // 读取日期和时间信息
            String date = scanner.next();
            String time = scanner.next();
            int sub = scanner.nextInt();

            // 解析日期信息
            String[] dateParts = date.split("-");
            int year = Integer.parseInt(dateParts[0]);
            int month = Integer.parseInt(dateParts[1]);
            int day = Integer.parseInt(dateParts[2]);

            // 解析时间信息
            String[] timeParts = time.split(":");
            int hour = Integer.parseInt(timeParts[0]);
            int minute = Integer.parseInt(timeParts[1]);
            int second = Integer.parseInt(timeParts[2]);

            // 计算总分钟数
            long allMinutes = generate(year, month, day, hour, minute, second, sub);
            // 消减时间差
            long adjustedMinutes = allMinutes - (allMinutes % sub);

            // 计算对应的天数、小时和分钟
            long totalDays = adjustedMinutes / (24 * 60);
            long hours = (adjustedMinutes / 60) % 24;
            long minutes = adjustedMinutes % 60;

            // 初始化日期和时间
            long currentYear = 1970;
            long currentMonth = 1;
            long currentDay = 1;

            // 逐年累加,直到剩余天数不足一年
            while (totalDays >= 0) {
                int daysInYear = isLeapYear((int) currentYear) ? 366 : 365;
                if (totalDays < daysInYear) {
                    break;
                }
                totalDays -= daysInYear;
                currentYear++;
            }

            // 逐月累加,直到剩余天数不足一个月
            while (totalDays > 0) {
                int daysInMonth = getDaysInMonth((int) currentMonth, (int) currentYear);
                if (totalDays < daysInMonth) {
                    break;
                }
                totalDays -= daysInMonth;
                currentMonth++;
            }

            // 加上剩余的天数
            currentDay += totalDays;

            // 格式化输出结果
            System.out.printf("%04d-%02d-%02d %02d:%02d:%02d\n", (int) currentYear, (int) currentMonth, (int) currentDay, (int) hours, (int) minutes, 0);
        }

        scanner.close();
    }
}

C++版

cpp 复制代码
#include "bits/stdc++.h"
#define ll long long
using namespace std;

ll generate(int y,int M,int d,int h,int m,int s,int sub){ // 这里面有一个精致的细节,时间是从1月1日开始,而非0点0时0分!
    int D = 0; // 计算天数
    for(int i=1970; i<=y-1; ++i){ // 算年
        if( (i%4==0 && i%100!=0) ||  i%400==0){ // 是闰年,!!!这个判断相当重要
            D+=366;
        }else{ // 是平年
            D+=365;
        }
    }
    for(int i=1; i<=M-1; ++i){ // 算月!!!是到 M-1月,否则会多运算
        if(i==1 || i==3 || i==5 || i==7 || i==8 || i==10 || i==12) D+=31;
        else if(i==2&&((y%4==0 && y%100!=0) || y%400==0)) D+=29;
        else if(i==2) D+=28;
        else D+=30;
    }
    D+=(d-1); // 算日

    // 返回分钟
    ll num = D*(24*60) + h*60 + m;
    return num;
}

// 从新来过
// 这次从新来过
int main(){
    int t;
    cin>>t;
    while(t--){
        // 获得时间差
        int y,M,d,h,m,s,sub;
        scanf("%d-%d-%d %d:%d:%d %d",&y,&M,&d,&h,&m,&s,&sub);
        // 消减时间差
        ll all_min = generate(y,M,d,h,m,s,sub); // 生成总时间
        ll acc_time = all_min - (all_min%sub);

        // 现在已经取到时间,仅需顺流而上即可
        ll lday,lh,lm;
        lday = acc_time/(24*60); // 求出天数
        lh = acc_time/(60)%24; // 求出小时
        lm = acc_time%(60);// 求出分钟

        ll YYYY = 1970;
        ll MM = 1;
        ll dd = 1;
        ll HH = lh;
        ll mm = lm;
        ll ss = 0;

        while(lday>=0){ // 加年
            if( (YYYY%4==0 && YYYY%100!=0) || YYYY%400==0){
                if(lday<366) break;
                lday-=366;
            }else{
                if(lday<365) break;
                lday-=365;
            }
            YYYY++;
        }

        while(lday>0){ // 加月
            if(MM==1 || MM==3 || MM==5 || MM==7 || MM==8 || MM==10 || MM==12){ // 31天的月
                if(lday<31) break;
                lday-=31;
            }else if(MM==2 &&((YYYY%4==0 && YYYY%100!=0) || YYYY%400==0)){ // 闰年
                if(lday<29) break;
                lday-=29;
            }else if(MM==2){
                if(lday<28) break;
                lday-=28;
            }else{
                if(lday<30) break;
                lday-=30;
            }
            MM++; // 说实话,我对这一步,表示有点无法理解
        }
        dd += lday;
        printf("%04lld-%02lld-%02lld %02lld:%02lld:%02lld\n",YYYY,MM,dd,HH,mm,ss); // 切记,这里一定要换行
    }

    return 0;
}

// 记录,如何判断,闰年中的闰月
// 劝告,最好封装一下函数,因为越多的代码,就意味着可能有更多的纰漏
// 不过咱们那个时区计算,好像可有可无

【最后一题】拉马车 (蓝桥真题)

本题涉及知识点,栈、堆、队列、set集合,对此不了解的,请点击下方链接!

1、什么是栈、队列(基础了解)

2、栈、队列(深入了解)

3、Java中栈和队列的使用及区别(具体用法)

( •̀ ω •́ )y,一点一点学,都能掌握的

题目描述

小的时候,你玩过纸牌游戏吗?

有一种叫做"拉马车"的游戏,规则很简单,却很吸引小朋友。

其规则简述如下:

假设参加游戏的小朋友是 AA 和 BB ,游戏开始的时候,他们得到的随机的纸牌序列如下:

AA 方:[K,8,X,K,A,2,A,9,5,A][K,8,X,K,A,2,A,9,5,A]

BB 方:[2,7,K,5,J,5,Q,6,K,4][2,7,K,5,J,5,Q,6,K,4]

其中的 XX 表示 "10",我们忽略了纸牌的花色。

从 AA 方开始,A、BA、B双方轮流出牌。

当轮到某一方出牌时,他从自己的纸牌队列的头部拿走一张,放到桌上,并且压在最上面一张纸牌上(如果有的话)。

此例中,游戏过程:

AA 出 KK,BB 出 22,AA 出 88,BB 出 77,AA 出 XX,此时桌上的序列为:

K,2,8,7,XK,2,8,7,X

当轮到 BB 出牌时,他的牌 KK 与桌上的纸牌序列中的 KK 相同,则把包括 KK 在内的以及两个 KK 之间的纸牌都赢回来,放入自己牌的队尾。注意:为了操作方便,放入牌的顺序是与桌上的顺序相反的。

此时,A、BA、B双方的手里牌为:

AA 方:[K,A,2,A,9,5,A][K,A,2,A,9,5,A]

B 方:[5,J,5,Q,6,K,4,K,X,7,8,2,K][5,J,5,Q,6,K,4,K,X,7,8,2,K]

赢牌的一方继续出牌。也就是 BB 接着出 55,AA 出 KK,BB 出 JJ,AA 出 AA,BB 出 55,又赢牌了。此时桌上的序列为:

5,K,J,A,55,K,J,A,5

此时双方手里牌:

AA 方:[2,A,9,5,A][2,A,9,5,A]

BB 方:[Q,6,K,4,K,X,7,8,2,K,5,A,J,K,5][Q,6,K,4,K,X,7,8,2,K,5,A,J,K,5]

注意:更多的时候赢牌的一方并不能把桌上的牌都赢走,而是拿走相同牌点及其中间的部分。但无论如何,都是赢牌的一方继续出牌,有的时候刚一出牌又赢了,也是允许的。

当某一方出掉手里最后一张牌,但无法从桌面上赢取牌时,游戏立即结束。

对于本例的初始手牌情况下,最后 AA 会输掉,而 BB 最后的手里牌为:

9K2A62KAX58K57KJ59K2A62KAX58K57KJ5

本题的任务就是已知双方初始牌序,计算游戏结束时,赢的一方手里的牌序。当游戏无法结束时,输出 -1。

输入描述

输入为 2 行,2 个串,分别表示 A、BA、B 双方初始手里的牌序列。我们约定,输入的串的长度不超过 30。2J9A7QA6Q6889977

输出描述

输出为 1 行,1 个串,表示 AA 先出牌,最后赢的一方手里的牌序。

输入输出样例

示例
输入

96J5A898QA
6278A7Q973

输出

2J9A7QA6Q6889977

Java版

java 复制代码
import java.util.*;
// 过程模拟-题型
/* 我的思考过程
  1、先将A、B两个字符串存入,存入什么容器中不知道!
  2、如何对 A-B放牌的缓存区 进行存、取,我好像也不太清楚
  3、怎么判断结束,好像也不清楚
*/
/*
  根据在草稿纸上,模拟出来的特性,
  1、A、B的存入取出,先进先出-适合队列
  2、A-B放牌的缓存区,先进后出-适合用栈
  3、检查放牌区里面的是否已经存入该牌,用set集合
*/
public class CardGameSimulation {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        // 读取玩家 A 和玩家 B 的初始牌串
        String s_A = scanner.next();
        String s_B = scanner.next();
        scanner.close();

        // 创建队列来存储玩家 A 和玩家 B 的牌,队列具有先进先出的特性
        Queue<Character> A = new LinkedList<>();
        Queue<Character> B = new LinkedList<>();
        // 创建栈来作为出牌的缓存区,栈具有先进后出的特性
        Stack<Character> storage = new Stack<>();
        // 创建集合来检查出牌缓存区中是否已经存在某张牌
        Set<Character> repeated = new HashSet<>();

        // 将玩家 A 的牌存入队列 A
        for (char c : s_A.toCharArray()) {
            A.add(c);
        }
        // 将玩家 B 的牌存入队列 B
        for (char c : s_B.toCharArray()) {
            B.add(c);
        }

        // 标记游戏是否结束
        boolean game_over = false;
        // 计时器,记录出牌的轮数
        int flag = 0;

        // 只要玩家 A 和玩家 B 都还有牌,并且游戏没有结束,就继续循环
        while (!A.isEmpty() && !B.isEmpty() && !game_over) {
            if (flag % 2 == 0) { // 偶数轮,玩家 A 出牌
                // 从玩家 A 的队列中取出一张牌
                char c = A.poll();
                // 检查出牌缓存区中是否已经存在这张牌
                if (!repeated.contains(c)) {
                    // 如果不存在,将牌放入出牌缓存区和查重集合
                    storage.push(c);
                    repeated.add(c);
                } else {
                    // 如果存在,将这张牌放回玩家 A 的队列,并从缓存区取出相关牌
                    A.add(c);
                    get_storage(A, storage, repeated, c);
                    continue; // 跳过本次循环的剩余部分,不增加计数器
                }
            } else if (flag % 2 == 1) { // 奇数轮,玩家 B 出牌
                // 从玩家 B 的队列中取出一张牌
                char c = B.poll();
                // 检查出牌缓存区中是否已经存在这张牌
                if (!repeated.contains(c)) {
                    // 如果不存在,将牌放入出牌缓存区和查重集合
                    storage.push(c);
                    repeated.add(c);
                } else {
                    // 如果存在,将这张牌放回玩家 B 的队列,并从缓存区取出相关牌
                    B.add(c);
                    get_storage(B, storage, repeated, c);
                    continue; // 跳过本次循环的剩余部分,不增加计数器
                }
            }
            flag++;
            // 如果出牌轮数超过 1000000 次,认为游戏陷入死循环,结束游戏
            if (flag >= 1000000) {
                game_over = true;
            }
        }

        // 输出游戏结果
        if (game_over) {
            System.out.println(-1);
        } else if (!A.isEmpty()) { // 玩家 A 胜利,输出玩家 A 的牌
            while (!A.isEmpty()) {
                System.out.print(A.poll());
            }
        } else if (!B.isEmpty()) { // 玩家 B 胜利,输出玩家 B 的牌
            while (!B.isEmpty()) {
                System.out.print(B.poll());
            }
        }
    }

    // 从栈中取出数据,从集合中删除对应元素,并将数据存入队列
    public static void get_storage(Queue<Character> in_q, Stack<Character> out_s, Set<Character> se, char c1) {
        char c;
        do {
            // 从栈中取出顶部元素
            c = out_s.pop();
            // 从集合中删除该元素
            se.remove(c);
            // 将元素存入队列
            in_q.add(c);
        } while (c != c1);
    }
}

C++版

cpp 复制代码
#include <iostream>
#include <queue>
#include <stack>
#include <set>
using namespace std;
// 过程模拟-题型
/* 我的思考过程
  1、先将A、B两个字符串存入,存入什么容器中不知道!
  2、如何对 A-B放牌的缓存区 进行存、取,我好像也不太清楚
  3、怎么判断结束,好像也不清楚
*/
/*
  根据在草稿纸上,模拟出来的特性,
  1、A、B的存入取出,先进先出-适合队列
  2、A-B放牌的缓存区,先进后出-适合用栈
  3、检查放牌区里面的是否已经存入该牌,用set集合
*/
void get_storage(queue<char>& in_q, stack<char>& out_s, set<char>& se,char c1){
    // 作用 - 从stack中去数据,删掉set中得内容,并存入queue
    char c;
    do{
        c = out_s.top(); // 取出
        out_s.pop();
        se.erase(c); // 删掉
        in_q.push(c); // 存入
    }while(c!=c1);
}

int main()
{
    string s_A;
    string s_B;
    cin>>s_A>>s_B;
//    scanf("%s",&s_A);
//    scanf("%s",&s_B);
    queue<char> A;
    queue<char> B;
    stack<char> storage; // 出牌的缓存区
    set<char> repeated; // 查重
    // 存入
    for(char c : s_A) A.push(c);
    for(char c : s_B) B.push(c);

    // 无线循环
    bool game_over = false;
    int flag = 0; // 设置一个计时器
    while(!A.empty()&&!B.empty()&&!game_over){
        if(flag%2==0){ // 偶数时,A出牌
            // 从A中取出这张牌
            // 判断 出牌的缓存区中,是否有这张牌
            // 将这张牌放入 出牌的缓存区中
            char c = A.front(); // 取
            A.pop();
            if(repeated.find(c)==repeated.end()){
                storage.push(c);
                repeated.insert(c); // 如果 出牌的缓存区中 无该牌,插入
            } else { // 创建一个函数,用于存取
                A.push(c);
                get_storage(A,storage,repeated,c);
                continue; // 用continue跳过,模拟题目要求,胜利过后,就不用++flag了。
            }
        }else if(flag%2==1){ // 奇数时,B出牌
            // 出B的时候,与A是同样的方法
            char c = B.front(); // 取
            B.pop();
            if(repeated.find(c)==repeated.end()){
                storage.push(c);
                repeated.insert(c); // 如果 出牌的缓存区中 无该牌,插入
            }  else { // 创建一个函数,用于存取
                B.push(c);
                get_storage(B,storage,repeated,c);
                continue; // 用continue跳过,模拟题目要求,胜利过后,就不用++flag了。
            }
        }
        flag++;
        if(flag>=1e6){ // 都循环这么多次了,还不结束,那肯定不对劲
            game_over = true;
        }
    }
    // 输出
    if(game_over) cout<<-1<<endl;
    else if(!A.empty()){ // A胜利,遍历A
        while(!A.empty()){
            char c = A.front();
            A.pop();
            cout<<c;
        }
    }else if(!B.empty()){ // B胜利,遍历B
        while(!B.empty()){
            char c = B.front();
            B.pop();
            cout<<c;
        }
    }

    return 0;
}

借鉴博客、视频、网站:

1、蓝桥官网

2、模拟算法 (算法详解+例题)

3、C++算法:模拟


( •̀ ω •́ )✧点击这里,继续学习其他模块吧!

相关推荐
liruiqiang0512 分钟前
线性模型 - 支持向量机
人工智能·算法·机器学习·支持向量机
明月看潮生16 分钟前
青少年编程与数学 02-010 C++程序设计基础 12课题、输入输出
开发语言·c++·青少年编程·编程与数学
梅茜Mercy32 分钟前
数据结构:二叉树的数组结构以及堆的实现详解
数据结构·算法
灿灿的金39 分钟前
pip 与当前python环境版本不匹配,python安装库成功,还是提示没有该库
开发语言·python·pip
小黄人软件1 小时前
C++ openssl AES/CBC/PKCS7Padding 256位加密 解密示例 MD5示例
开发语言·c++
姜来可期1 小时前
Go Test 单元测试简明教程
开发语言·后端·学习·golang·单元测试
walkskyer1 小时前
Golang `testing`包使用指南:单元测试、性能测试与并发测试
开发语言·golang·单元测试
飞升不如收破烂~1 小时前
WebSocketHandler 是 Spring Framework 中用于处理 WebSocket 通信的接口
开发语言
没明白白1 小时前
希尔排序:突破插入排序的局限
数据结构·算法·排序算法
dokii11 小时前
leetcode541 反转字符串2
java·数据结构·算法