P1540 [NOIP 2010 提高组] 机器翻译

记录154

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

queue<int>q; // 使用队列维护内存中的单词(FIFO)
bool vis[1001]; // 标记数组,记录单词是否在内存中(0表示不在)

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    
    int m,n;
    cin>>m>>n; // 输入内存容量和文章长度
    
    int ans=0; // 记录查词典的次数
    for(int i=1;i<=n;i++){ // 处理每个单词
        int x;
        cin>>x; // 读取当前单词
        
        if(!vis[x]){ // 如果单词不在内存中
            ans++; // 需要查词典
            vis[x]=true; // 标记为在内存中
            q.push(x); // 加入队列
            
            if(q.size()>m){ // 如果内存已满(超过M个单词)
                int top=q.front(); 
                q.pop(); // 移除最早进入的单词(队首)
                vis[top]=false; // 更新标记,表示该单词已被踢出内存
            }
        }
    }
    
    cout<<ans; // 输出总查词典次数
    return 0;
}

题目传送门https://www.luogu.com.cn/problem/P1540


前言

我是一名专注信奥赛(CSP-J/S、NOIP)的教练。

  • 如果你觉得这篇题解对你有帮助,欢迎点击关注我的CSDN账号,我会持续更新高质量算法解析。
  • 我深知算法思维的构建远比单纯通过题目更重要,本系列题解不局限于AC代码的堆砌,而是致力于拆解题目背后的逻辑链条与核心知识点
  • 备赛路上若遇瓶颈,欢迎随时评论或私信,我将甄选典型疑难问题,通过视频讲解或撰写专项文章的形式,为你提供深度答疑。

核心解题思路

这道题是一道非常经典的**数据结构模拟(队列 + 哈希标记)**问题,完美体现了计算机操作系统中"缓存淘汰机制"的雏形。

  1. 问题转化与数据结构选择

    题目中内存的存取规则是:"若内存已满,清空最早进入 内存的那个单词"。这种"先进先出"(FIFO, First In First Out)的特性,天然对应了数据结构中的队列(Queue)。我们可以用一个队列来维护当前内存中单词的先后顺序。

  2. 算法设计(队列模拟 + 布尔数组标记)

    由于单词的数值大小不超过 1000,我们可以使用一个大小为 1001 的布尔数组 vis 作为哈希表(或称为标记数组),用来记录某个单词当前是否在内存中。

    遍历文章中的每个单词时:

    • 如果 vis[x] 为真,说明单词在内存中,直接跳过。
    • 如果 vis[x] 为假,说明内存中没有,需要查词典(答案 ans++)。随后将该单词压入队列,并将 vis[x] 设为真。
    • 关键淘汰机制 :每次新单词入队后,检查队列大小是否超过了内存容量 M。如果超过了,说明内存满了,必须将队首(最早进入的)单词弹出,并将其在 vis 数组中的标记清除。

代码分块详细解释

1. 头文件与全局数据结构定义

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
queue<int> q; // 使用队列维护内存中的单词(FIFO)
bool vis[1001]; // 标记数组,记录单词是否在内存中(0表示不在)
  • 详细分析queue<int> 是 C++ STL 提供的队列容器,支持 push(入队)、pop(出队)、front(获取队首)等操作,完美契合题目要求。bool vis[1001] 作为一个全局数组,默认会被初始化为 false,这恰好对应了"翻译开始前,内存中没有任何单词"的初始状态。

2. 主函数与变量初始化

cpp 复制代码
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    
    int m, n;
    cin >> m >> n; // 输入内存容量和文章长度
    
    int ans = 0; // 记录查词典的次数
  • 详细分析m 代表内存容量,n 代表文章长度。ans 初始化为 0,用于累计最终需要去外存查找词典的次数。

3. 核心逻辑:遍历单词与内存状态维护

cpp 复制代码
    for(int i = 1; i <= n; i++){ // 处理每个单词
        int x;
        cin >> x; // 读取当前单词
        
        if(!vis[x]){ // 如果单词不在内存中
            ans++; // 需要查词典
            vis[x] = true; // 标记为在内存中
            q.push(x); // 加入队列
            
            if(q.size() > m){ // 如果内存已满(超过M个单词)
                int top = q.front(); 
                q.pop(); // 移除最早进入的单词(队首)
                vis[top] = false; // 更新标记,表示该单词已被踢出内存
            }
        }
    }
  • 详细分析 :这是代码的灵魂所在,完美模拟了翻译软件的缓存逻辑。
    • 内存命中判断if(!vis[x]) 是程序的入口。如果 vis[x] 已经是 true,说明这个单词还在内存里,无需任何操作,直接处理下一个单词。
    • 查词典与入队 :如果不在内存中,ans 加 1,并将该单词的状态标记为 true,同时通过 q.push(x) 将其放入队列尾部。
    • 内存淘汰机制 :新单词入队后,队列的长度可能变为 M+1。此时必须执行 q.pop() 将队首元素(最早进入内存的单词)踢出。特别注意 :在弹出队首元素 top 后,必须同步执行 vis[top] = false。如果不取消标记,当这个被踢出的单词在文章后面再次出现时,程序会错误地认为它还在内存中,从而导致答案错误。

4. 输出结果

cpp 复制代码
    cout << ans; // 输出总查词典次数
    return 0;
}
  • 详细分析 :循环结束后,ans 中存储的就是整个翻译过程中,因为内存缺失而去外存查找词典的总次数。

核心逻辑总结表

代码模块 核心变量/操作 精炼作用 解决的痛点
内存缓存模拟 queue<int> q 利用队列的 FIFO 特性存储单词 完美还原了"新单词入内存,满员时踢出最早单词"的物理规则
快速查找标记 bool vis[1001] 记录每个单词当前是否在内存中 避免了每次都要遍历队列去查找单词,将查找复杂度降为 O(1)
内存命中判断 if(!vis[x]) 判断当前单词是否需要查词典 只有当内存中确实不存在该单词时,才触发后续的查词与入队逻辑
内存淘汰机制 q.pop()vis[top]=false 移除队首元素并同步清除其标记 保证了内存容量不超过 M,且被踢出的单词在后续再次出现时能被正确识别为"不在内存"
查词计数 ans++ 累加内存未命中的次数 统计出最终需要访问外存词典的总次数
相关推荐
晚笙coding1 小时前
LeetCode 98:验证二叉搜索树 —— 从局部判断到全局范围约束的递归思想
算法·leetcode·职场和发展
学计算机的计算基1 小时前
回溯算法下篇:四道经典题讲透约束剪枝、原地标记、预计算与状态压缩
java·笔记·算法
咕白m6252 小时前
通过 C++ 写入数据到 Excel 文档
c++·后端
霸道流氓气质2 小时前
SpringBoot中实现告警判定算法-位报警与模拟量阈值-技术详解
spring boot·算法·jquery
行者全栈架构师2 小时前
混元 Hy3 Agent 实战:季度报告 3 小时变 40 分钟
算法·架构·代码规范
兰令水2 小时前
hot100【acm版】【2026.7.21打卡-java版本】
java·开发语言·算法·leetcode·面试
qz5zwangzihan12 小时前
题解:Atcoder Beginner Contest abc467 F - Email Scheduling Optimization
c++·贪心·atcoder·平衡树·abc467·abc467_f·fhq-treap
好好沉淀3 小时前
主键选择(自增 vs UUID vs 雪花算法)
java·算法
郝学胜-神的一滴3 小时前
中级OpenGL教程 023:Assimp模型加载全解——从源码到架构的骈文探秘
c++·unity·游戏引擎·cmake·unreal engine·opengl