洛谷算法1-1 模拟与高精度(NOIP经典真题解析)java(持续更新)

技术笔记:算法1-1 模拟与高精度(NOIP经典真题解析)

模拟算法是编程竞赛中最基础、最常用的算法之一,核心思想是严格按照题目描述的规则,一步步还原问题的执行过程,不需要进行复杂的逻辑推导或优化,只需要准确、细致地实现题目要求。本文将通过三道NOIP经典真题(乒乓球11分制/21分制、扫雷游戏、玩具小人找眼镜),讲解模拟算法的实际应用,帮助理解模拟类题目的解题思路和代码实现技巧。

一、 乒乓球比赛结果统计(NOIP2003 普及组 T1)

题目核心要求

输入由W(华华得分)、L(对手得分)、E(结束标志)组成的字符串,分别按照11分制和21分制统计比赛结果:

  1. 一局比赛的结束条件:某方得分≥11(11分制)/≥21(21分制),且双方分差≥2
  2. 一局结束后立即开始下一局,最终输出所有已结束的局的比分,以及当前未结束局的比分
  3. 忽略E之后的所有字符

解题思路

  1. 首先读取输入,拼接成完整的有效字符串(仅保留E之前的W和L)
  2. 分别实现11分制和21分制的模拟统计:
    • 初始化双方得分为0
    • 遍历有效字符串,逐个更新得分
    • 判断是否满足局的结束条件,满足则输出当前比分并重置得分
    • 遍历结束后,输出最后一局未完成的比分
  3. 注意两部分结果之间用空行分隔

完整Java代码

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

/**
 * @author xuqiang
 * @date 2026/2/3  15:50
 * @description 乒乓球比赛结果统计(NOIP2003 普及组 T1)
 */
public class Main3 {
    public static void main(String[] args){
        Scanner sc = new Scanner(System.in);
        StringBuilder str = new StringBuilder(); // 用于拼接所有有效输入字符(W/L)
        while (sc.hasNext()) {
            String s = sc.next();
            for (int i = 0; i < s.length(); i++) {
                char c = s.charAt(i);
                if (c == 'E') { // 遇到E,终止输入读取,开始处理结果
                    go(str.toString());
                    sc.close();
                    return;
                }
                if (c == 'W' || c == 'L') { // 仅保留有效得分字符
                    str.append(c);
                }
            }
        }
        sc.close();
        go(str.toString()); // 处理未遇到E的情况(输入末尾无E)
    }

    /**
     * 核心模拟方法:分别处理11分制和21分制的比分统计
     * @param s 有效得分字符串(仅包含W/L)
     */
    public static void go(String s){
        // 第一部分:11分制模拟
        int w = 0,l = 0;
        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            if(c =='W') w++; // 华华得分+1
            else l++; // 对手得分+1
            
            // 11分制结束条件:某方≥11 且 分差≥2(关键判断,缺一不可)
            if((w >= 11 || l >= 11) && Math.abs(w-l) >= 2){
                System.out.println(w+":"+l);
                w = 0; // 重置得分,开始下一局
                l = 0;
            }
        }
        System.out.println(w+":"+l); // 输出最后一局未完成的比分

        System.out.println(); // 两部分结果之间的空行(符合题目输出格式)

        // 第二部分:21分制模拟(逻辑与11分制一致,仅修改分数阈值)
        w = 0;
        l = 0;
        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            if(c =='W') w++;
            else l++;
            
            // 21分制结束条件:某方≥21 且 分差≥2(关键判断)
            if((w >= 21 || l >= 21) && Math.abs(w-l) >= 2){
                System.out.println(w+":"+l);
                w = 0;
                l = 0;
            }
        }
        System.out.println(w+":"+l); // 输出最后一局未完成的比分
    }
}

关键注意点

  1. 输入处理时,必须提前终止E之后的读取,且忽略非W/L/E的字符(符合题目附注要求)
  2. 局的结束条件是两个条件同时满足,缺一不可(例如11:10不满足分差≥2,不能结束一局)
  3. 两部分结果之间必须有且仅有一个空行,符合题目输出格式要求

二、 扫雷游戏(NOIP2015 普及组 T2)

题目核心要求

给定n行m列的雷区(*表示地雷,?表示非地雷),计算每个非地雷格周围8个方向的地雷数量,最终输出完整雷区(*保留,?替换为周围地雷数)。

解题思路

  1. 读取输入的雷区尺寸n和m,以及n行雷区数据,存储到二维数组中
  2. 遍历二维数组中的每一个格子:
    • 若是地雷(*),直接保留,不做处理
    • 若是非地雷(原?,代码中先替换为0),统计其上下左右、左上右上、左下右下8个方向的地雷数量
  3. 将统计的地雷数量转换为字符,替换原非地雷格,最终输出完整二维数组

完整Java代码

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

/**
 * @author xuqiang
 * @date 2026/1/31  19:20
 * @description 扫雷游戏(NOIP2015 普及组 T2)
 */
public class Main {
    // 定义二维数组存储雷区,大小105*105(略大于题目最大100*100,避免边界判断越界)
    private static char juzhen[][] = new char[105][105];
    private static int n,m; // 雷区的实际行数和列数

    /**
     * 统计单个非地雷格周围的地雷数量并更新数组
     * @param y 当前格子的行索引
     * @param x 当前格子的列索引
     */
    public static void countMine(int y,int x){
        // 仅处理非地雷格(地雷格直接跳过)
        if(juzhen[y][x] != '*'){
            int count = 0; // 周围地雷数量计数器
            // 遍历8个方向(dy: -1,0,1;dx: -1,0,1)
            for (int dy = -1; dy <= 1; dy++) {
                for (int dx = -1; dx <= 1; dx++) {
                    // 跳过当前格子本身(dy=0且dx=0)
                    if(dy == 0 && dx == 0) continue;
                    
                    int nx = x + dx; // 相邻格子的列索引
                    int ny = y + dy; // 相邻格子的行索引
                    
                    // 边界判断:确保相邻格子在雷区范围内(避免数组下标越界,关键)
                    if(ny >= 0 && ny < n && nx >= 0 && nx < m){
                        if(juzhen[ny][nx] == '*'){ // 相邻格子是地雷,计数器+1
                            count++;
                        }
                    }
                }
            }
            // 将计数器转换为字符('0' + count 实现int到char的数字转换)
            juzhen[y][x] = (char) ('0' + count);
        }
    }

    public static void main(String[] args){
        Scanner sc = new Scanner(System.in);
        n = sc.nextInt();
        m = sc.nextInt();
        sc.nextLine(); // 吸收换行符,避免读取后续字符串时出现异常

        // 第一步:读取雷区数据,初始化二维数组
        for (int i = 0; i < n; i++) {
            String line = sc.next();
            for (int j = 0; j < m; j++) {
                juzhen[i][j] = line.charAt(j); // 提取每行的第j个字符,赋值给二维数组
                // 非地雷格先替换为'0',方便后续统计更新
                if(juzhen[i][j] == '?'){
                    juzhen[i][j] = '0';
                }
            }
        }

        // 第二步:遍历所有格子,统计非地雷格周围地雷数量
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m ; j++) {
                countMine(i,j);
            }
        }

        // 第三步:输出最终雷区结果
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                System.out.print(juzhen[i][j]);
            }
            System.out.println(); // 每行输出完毕后换行,符合题目格式
        }
        sc.close();
    }
}

关键注意点

  1. 二维数组大小定义为105*105,是为了简化边界判断,避免因索引越界导致运行时异常
  2. 统计8个方向时,必须跳过当前格子本身(dy=0且dx=0),否则会误将当前格子计入统计
  3. int类型的地雷数量转换为char类型时,使用'0' + count,利用ASCII码的特性实现快速转换(避免复杂的字符串拼接)
  4. 读取输入时,注意吸收换行符,避免后续sc.next()读取到空字符串

三、 玩具小人找眼镜(NOIP2016 提高组 D1T1)

题目核心要求

n个玩具小人围成一圈,每个小人有朝向(0朝内、1朝外)和职业,给定m条指令(向左/右数s个),模拟指令执行过程,最终输出到达的小人职业。关键规则:

  1. 朝向决定左右方向:
    • 0(朝内):左=顺时针,右=逆时针
    • 1(朝外):左=逆时针,右=顺时针
  2. 小人按逆时针顺序输入,围成一圈
  3. 从第一个读入的小人开始执行指令

解题思路

  1. 读取n和m,然后读取n个小人的朝向和职业,分别存储到两个数组中
  2. 初始化当前小人的索引为0(对应第一个读入的小人)
  3. 遍历每条指令,根据当前小人的朝向和指令内容,计算新的当前索引:
    • 核心逻辑:判断指令方向与当前小人朝向是否一致,确定索引的增减方向
    • 利用取模运算处理环形结构,避免索引越界
  4. 指令执行完毕后,输出当前索引对应的职业

完整Java代码

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

/**
 * @author xuqiang
 * @date 2026/2/3  14:06
 * @description 玩具小人找眼镜(NOIP2016 提高组 D1T1)
 */
public class Main2 {
    public static void main(String[] args){
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt(); // 玩具小人个数
        int m = sc.nextInt(); // 指令条数

        int[] dir = new int[n]; // 存储每个小人的朝向:0朝内,1朝外
        String[] job = new String[n]; // 存储每个小人的职业

        // 第一步:读取n个小人的朝向和职业
        for (int i = 0; i < n; i++) {
            dir[i] = sc.nextInt(); // 读取朝向
            job[i] = sc.next(); // 读取职业
        }

        int curr = 0; // 当前所在小人的索引,初始为第一个读入的小人(索引0)
        // 第二步:执行m条指令
        for (int i = 0; i < m; i++) {
            int a = sc.nextInt(); // 指令方向:0左数,1右数
            int s = sc.nextInt(); // 指令数:数s个小人

            // 核心逻辑:判断当前小人朝向与指令方向是否一致,确定索引移动方向
            // 一致时,索引反向移动(s取负);不一致时,索引正向移动(s取正)
            if(dir[curr] == a){
                // 移动后索引 = (当前索引 - s + n) % n (+n是为了避免负数取模出现异常,关键)
                curr = (curr - s + n) % n;
            }else{
                // 移动后索引 = (当前索引 + s) % n (环形结构,取模n保证索引在0~n-1范围内)
                curr = (curr + s) % n;
            }
        }

        // 第三步:输出最终当前小人的职业
        System.out.println(job[curr]);
        sc.close();
    }
}

关键注意点

  1. 环形结构的索引处理:使用% n保证索引始终在0 ~ n-1范围内,避免越界;当索引可能为负数时,先+ n% n,确保结果为非负数
  2. 核心逻辑简化:无需纠结顺时针/逆时针的具体方向,只需通过当前小人朝向与指令方向是否一致来确定索引移动方向,大幅简化代码
  3. 输入顺序是逆时针,与数组索引顺序一致,直接存储即可,无需额外调整顺序
  4. 指令中的s满足1≤s<n,无需处理s≥n的情况,简化了取模运算的复杂度

四、 模拟算法核心总结

  1. 模拟算法的核心是**"忠实还原题目规则"**,解题前需先理清题目中的所有条件和规则,避免遗漏关键细节(如局的结束条件、方向规则、边界判断)。
  2. 输入处理是模拟题的基础,需注意有效数据提取、换行符处理、结束标志判断,避免因输入问题导致程序异常。
  3. 边界处理是模拟题的常见坑点,需注意数组下标越界、环形结构索引、负数取模等问题,通常可通过扩大数组大小、取模运算、提前补正数值等方式解决。
  4. 代码实现时,可将重复逻辑提取为独立方法(如扫雷的统计方法、乒乓球的比分处理方法),提高代码可读性和可维护性。
相关推荐
razelan2 小时前
初级算法技巧 4
算法
砍树+c+v2 小时前
3a 感知机训练过程示例(手算拆解,代码实现)
人工智能·算法·机器学习
zy_destiny2 小时前
【工业场景】用YOLOv26实现4种输电线隐患检测
人工智能·深度学习·算法·yolo·机器学习·计算机视觉·输电线隐患识别
MengFly_2 小时前
Compose 脚手架 Scaffold 完全指南
android·java·数据库
沐知全栈开发2 小时前
Rust 函数
开发语言
PPPPickup2 小时前
application.yml或者yaml文件不显示绿色问题
java·数据库·spring
*小海豚*2 小时前
springcloud项目运行启动类无法启动,IDEA也没有任何提示
java·ide
智驱力人工智能2 小时前
货车违规变道检测 高速公路安全治理的工程实践 货车变道检测 高速公路货车违规变道抓拍系统 城市快速路货车压实线识别方案
人工智能·opencv·算法·安全·yolo·目标检测·边缘计算
罗湖老棍子2 小时前
【例9.18】合并石子(信息学奥赛一本通- P1274)从暴搜到区间 DP:石子合并的四种写法
算法·动态规划·区间dp·区间动态规划