蓝桥杯 缺页异常2【算法赛】

原题目链接

题目描述

你有一台拥有 n 个缓存页面空间的服务器,只有一个用户在同时使用。这个用户将会一共给出 m 条页面请求。每条请求用一个整数 y 表示请求了页面 y。请你计算出对于 n ∈ [0, m],使用 LRU 页面置换算法的缺页中断次数是多少。

LRU 算法描述:

每次当需要访问一个页面时,假设当前用户持有 n_i 个缓存空间,首先查询该页面是否存在于这 n_i 个缓存页面空间中:

  • 如果存在,则更新该页面的访问时间并结束;
  • 如果不存在,则将该页面置入 n_i 个位置中的一个,并记录当前访问时间,并触发一次缺页中断。

置入位置的选择规则如下:

  • 如果当前空间中有空的位置(为初始状态,历史未在这个位置插入过任何页面),则选择任意一个空位置插入;
  • 否则选择当前在缓存中并且之前最久未被访问过的页面(记录的访问时间距离现在最久),在该页面的位置插入新页面。

用户的初始缓存页面空间的每个位置均处在初始状态。

输入格式

  1. 第一行一个整数 m,表示页面请求条数。
  2. 接下来一行,包含 m 个整数,表示 m 条页面请求的编号。

输出格式

输出 m + 1 个整数,表示当缓存大小为 [0, m] 时的答案。

样例输入

in 复制代码
6
1 3 1 2 1 1

样例输出

out 复制代码
6 5 3 3 3 3 3

说明

当缓存大小为 2 时的执行过程如下:

  • 初始:[]
  • 访问:[1](缺页)
  • 访问:[3, 1](缺页)
  • 访问:[1, 3]
  • 访问:[2, 1](缺页)
  • 访问:[1, 2]
  • 访问:[1, 2]

故缺页中断次数为 3 次。

评测数据规模

  • 对于 30% 的评测数据,0 < m ≤ 1000
  • 对于 70% 的评测数据,0 < m ≤ 10^5
  • 对于 100% 的评测数据,0 < m, y ≤ 10^6

超时暴力写法

超时暴力写法代码

暴力写法的思路是模拟,先附上完整c++代码,时间复杂度:O( m 2 ∗ log ⁡ n m^{2} * \log n m2∗logn),其中 m 是页面请求数,n 是缓存大小。

cpp 复制代码
#include <bits/stdc++.h>

using namespace std;

int m;
vector<int> page_request_array;

int obtain_the_number_of_missing_pages(int n) {
    if (n == 0) return m;
    unordered_map<int, int> page_cache_unordered_map;
    map<int, int> page_cache_ordered_map;
    int ans = 0;
    for (int i = 0; i < m; i++) {
        if (page_cache_unordered_map.find(page_request_array[i]) == page_cache_unordered_map.end()) {
            if (page_cache_unordered_map.size() >= n) {
                int entry_time = page_cache_ordered_map.begin()->first;
                int page_number = page_cache_ordered_map.begin()->second;
                page_cache_unordered_map.erase(page_number);
                page_cache_ordered_map.erase(entry_time);
            }
            page_cache_unordered_map[page_request_array[i]] = i;
            page_cache_ordered_map[i] = page_request_array[i];
            ans++;
        }
        else {
            int page_number = page_request_array[i];
            int entry_time = page_cache_unordered_map[page_request_array[i]];
            page_cache_unordered_map.erase(page_number);
            page_cache_ordered_map.erase(entry_time);
            page_cache_unordered_map[page_number] = i;
            page_cache_ordered_map[i] = page_number;
        }
    }
    return ans;
}

int main() {
    cin >> m;
    page_request_array = vector<int>(m, 0);
    for (int i = 0; i < m; i++) {
        cin >> page_request_array[i];
    }
    for (int i = 0; i <= m; i++) {
        cout << obtain_the_number_of_missing_pages(i);
        if (i != m) cout << " ";
    }
    return 0;
}//by wqs

暴力模拟思路

  1. 初始化变量和数据结构
    • m 是页面请求的数量(即用户输入的页面请求总数)。
    • page_request_array 存储了所有的页面请求。
    • unordered_map<int, int> page_cache_unordered_map:页面队列的页面编号是唯一的,键是缓存中页面的编号,值是最近访问的时间,快速查找页面是否在缓存中,快速得到页面的最近访问时间。
    • map<int, int> page_cache_ordered_map:页面队列的最近访问时间也是唯一的,因为每一时间,最多只能进入一个页面,键是最近访问的时间,值是缓存中页面的编号。因为 map 会按键值自动排序,最小的键对应的页面就是最久未被访问的页面,适合用来实现 LRU。
  2. 暴力模拟每种缓存大小
    • 外层循环从 i = 0m,代表缓存大小从 0m
    • 对于每种缓存大小 i,你调用 obtain_the_number_of_missing_pages(i) 来计算缺页中断次数。
  3. 每次访问页面
    • 页面不在缓存中(缺页)
      • 使用 unordered_map.find() 来检查当前页面是否在缓存中。
      • 如果不在缓存中,并且缓存已满(即缓存中的页面数已达到 n),就从缓存中移除最久未使用的页面。
        • 最久未使用的页面由 page_cache_ordered_map.begin() 提供。begin() 返回 map 中第一个元素,即访问时间最早的页面。
        • unordered_mapmap 中删除该页面。
      • 然后将当前页面加入缓存,并记录当前访问的时间。
      • 缺页中断次数增加。
    • 页面已经在缓存中
      • 如果页面已经在缓存中,需要更新该页面的访问时间。首先从 unordered_map 中删除当前页面,然后重新插入到缓存中,并更新访问时间。
  4. 输出结果
    • 通过循环输出每种缓存大小下的缺页中断次数。缓存大小从 0m

代码中对于每种缓存大小进行计算并输出结果,是一种直接暴力求解的方法。时间复杂度较高,适用于规模较小的情况(例如 m <= 10^3)。

标准AC写法

AC思路

考虑你有一个 无限大的缓存,缓存里的页按照从上到下 最近访问时间依次增加 排布。即每次访问一个已经存在于缓存中的页面时,就把它提到最上面;每次插入新页面时,就将其插入在最上面。

对于任意的页面访问顺序,如果限制了缓存大小为 k,相当于只看这个无限大缓存空间的前 k 个。因此,只需要维护一个 无限大的缓存空间。每当出现一个页面访问请求时:

  • 如果这个页面从未出现过,则所有大小的缓存都会出现一次 缺页中断;将整个答案数组加一。

    cpp 复制代码
    dif_add(0, m);
  • 如果这个页面已经在缓存中,假设它出现在从上往下的第 t 个页面中,则缓存大小为 [0, t-1] 的缓存会出现一次页面中断。将答案数组[0, t - 1]加一。

这相当于一次 前缀加,由于只有在最后需要进行一次查询,使用 差分数组 可以在 O(m) 时间复杂度内维护中断次数。

cpp 复制代码
dif_add(0, ranking - 1);

剩下的问题就是如何知道当前访问的页面是从上往下的第几个页面。在思考之后可以发现:

  • 一个页面被访问时,只有在该页面上一次访问到这次访问之间不同的其他页面数不大于缓存大小时,页面才能存在于缓存中。

  • 所以,只需要处理出任意两个相同的页面之间不同的页面数的个数。

    in 复制代码
    例如页面顺序3 1 4 1 4 3
    问你第二个三进来的时候,第一个三在缓存中的第几个位置。
    简单模拟
    3
    1 3
    4 1 3
    1 4 3
    4 1 3
    所以在第3个位置
    数学规律是:上一个3和这一个3之间有2个不同的数字1和4,所以排名就是2 + 1 = 3;
    再比如
    问你第二个4进来的时候,第一个4在缓存中的第几个位置。
    简单模拟
    3
    1 3
    4 1 3
    1 4 3
    所以在第2个位置
    数学规律是:上一个4和这一个4之间有1个不同的数字1,所以排名就是1 + 1 = 2;

这种情况可以使用多种方法来解决。一个经典且快速的做法是:

  • 从左到右扫数组,每次将每种数字的 最后一个 设为 1,其他设为 0。这样,我们可以直接求出区间内不同页面的个数。

也就是多次区间查询+单点修改,可以使用线段树。来维护单点修改和区间求和。

in 复制代码
page_cache_unordered_map可以查出上一个的下表。
int last_index = page_cache_unordered_map[p];
记录下来,然后更新page_cache_unordered_map
page_cache_unordered_map[p] = i;
将最后一个设为 1
add(1, 1, m, i, i, 1);//方法是0+1就等于1了
将上一个1重新设置为0
add(1, 1, m, last_index, last_index, -1);//方法是1-1就等于0了
如果你要查上次到这次的不同页面个数
ask(1, 1, m, last_index, i);就是求区间和,就会返回你想要的。

AC代码

最终的复杂度为 O(m * log(V)),其中:

  • m 是页面请求的数量;
  • V 是页面的值域,在本题中,V 最大为 10^6。
cpp 复制代码
#include<bits/stdc++.h>

using namespace std;

struct node {
    int val;
    int lazy;
};

int m, p;
vector<int> missing_page_count_dif;
unordered_map<int, int> page_cache_unordered_map;
vector<node> nodes;

void dif_add(int start, int end) {
    if (start + 1 <= m + 1) missing_page_count_dif[start + 1]++;
    if (end + 2 <= m + 1) missing_page_count_dif[end + 2]--;
}

void pssslazy(int cur, int cur_l, int cur_r) {
    int middle = (cur_l + cur_r) / 2;
    if (nodes[cur].lazy > 0) {
        nodes[2 * cur].val += (middle - cur_l + 1) * nodes[cur].lazy;
        nodes[2 * cur + 1].val += (cur_r - middle) * nodes[cur].lazy;
        nodes[2 * cur].lazy += nodes[cur].lazy;
        nodes[2 * cur + 1].lazy += nodes[cur].lazy;
        nodes[cur].lazy = 0;
    }
}

void add(int cur, int cur_l, int cur_r, int left, int right, int k) {
    int middle = (cur_l + cur_r) / 2;
    if (cur_l >= left && cur_r <= right) {
        nodes[cur].val += (cur_r - cur_l + 1) * k;
        nodes[cur].lazy += k;
        return;
    }
    pssslazy(cur, cur_l, cur_r);
    if (left <= middle) add(2 * cur, cur_l, middle, left, right, k);
    if (right > middle) add(2 * cur + 1, middle + 1, cur_r, left, right, k);
    nodes[cur].val = nodes[2 * cur].val + nodes[2 * cur + 1].val;
}

int ask(int cur, int cur_l, int cur_r, int left, int right) {
    int middle = (cur_l + cur_r) / 2;
    if (cur_l >= left && cur_r <= right) return nodes[cur].val;
    pssslazy(cur, cur_l, cur_r);
    int ans = 0;
    if (left <= middle) ans += ask(2 * cur, cur_l, middle, left, right);
    if (right > middle) ans += ask(2 * cur + 1, middle + 1, cur_r, left, right);
    return ans;
}

int main() {
    cin >> m;
    missing_page_count_dif = vector<int>(m + 2, 0);
    nodes = vector<node>(4 * m + 4);
    for (int i = 1; i <= m; i++) {
        cin >> p;
        if (page_cache_unordered_map.find(p) == page_cache_unordered_map.end()) {
            page_cache_unordered_map[p] = i;
            add(1, 1, m, i, i, 1);
            dif_add(0, m);
        }
        else {
            int last_index = page_cache_unordered_map[p];
            page_cache_unordered_map[p] = i;
            add(1, 1, m, last_index, last_index, -1);
            add(1, 1, m, i, i, 1);
            int ranking = ask(1, 1, m, last_index, i);
            dif_add(0, ranking - 1);
        }
    }
    for (int i = 1; i <= m + 1; i++) {
        missing_page_count_dif[i] += missing_page_count_dif[i - 1];
        cout << missing_page_count_dif[i];
        if (i != m + 1) cout << " ";
    }
    return 0;
}//by wqs
相关推荐
Mh_ithrha2 小时前
题目:小鱼比可爱(java)
java·开发语言·算法
l1t2 小时前
数独优化求解C库tdoku-lib的使用
c语言·开发语言·python·算法·数独
有一个好名字2 小时前
力扣-奇偶链表
算法·leetcode·链表
wxm6312 小时前
力扣算法题(C++):1、2
java·算法·leetcode
im_AMBER2 小时前
Leetcode 103 反转链表 II
数据结构·c++·笔记·学习·算法·leetcode
rgeshfgreh2 小时前
回溯算法精解:排列、子集与组合
python·算法·深度优先
rit84324992 小时前
有限元算法求解铁木辛柯梁梁静力问题实例
算法
智驱力人工智能2 小时前
矿山皮带锚杆等异物识别 从事故预防到智慧矿山的工程实践 锚杆检测 矿山皮带铁丝异物AI预警系统 工厂皮带木桩异物实时预警技术
人工智能·算法·安全·yolo·目标检测·计算机视觉·边缘计算
忆锦紫2 小时前
图像降噪算法:中值滤波算法及MATLAB实现
图像处理·算法·matlab