题目
题目链接:TUOJ
https://sim.csp.thusaac.com/contest/36/problem/2
参考:CCF-CSP第36次认证第三题------缓存模拟(满分题解)_ccfcsp缓存模拟-CSDN博客
思路
1. 数据结构设计
核心存储结构
-
cache[NN]:每个组(共N组)维护一个set<int>,记录该组当前缓存了哪些内存块 -
lru[NN]:每个组维护一个按访问时间排序的set<pair<时间戳, 内存块>>,用于实现 LRU 替换策略
辅助映射表
-
tim[pos]:记录每个内存块pos的最后访问时间戳 -
change[pos]:脏位标记,记录内存块是否被修改过(写回时需要写回内存)
2. 关键算法逻辑
地址映射
给定内存块地址 pos:
-
组索引 :
id = (pos / n) % N -
每组可容纳
n个内存块(n 路组相联)
缓存访问流程
-
判断命中:
-
若
cache[id]中存在pos→ 缓存命中 -
若未命中 → 需要加载或替换
-
-
未命中处理:
-
如果该组已满(
cache[id].size() == n),调用pop(id)执行 LRU 替换:-
弹出
lru[id]中时间戳最小的块(最久未使用) -
若该块被修改过(
change[pos] == 1),则输出"1 pos"表示写回内存,并清除脏位 -
从
cache[id]中移除该块
-
-
加载新块:输出
"0 pos"表示从内存读取,并将其加入cache[id]
-
-
更新访问信息:
-
更新该块的时间戳
tim[pos] = nowtime -
将
(tim[pos], pos)插入lru[id](若已在缓存中,需先删除旧记录再插入)
-
3. 操作类型
-
o == 1:写操作,标记change[pos] = 1(脏位) -
默认或
o == 0:读操作(代码中未显式判断o==0,但o==1之外的均视为读)
4. 输出格式
-
0 pos:表示从内存加载块pos到缓存 -
1 pos:表示将脏块pos写回内存
代码
【对上面的代码按照自己的习惯进行了改写,但是逻辑基本一致】
可以看到代码量并不大,可惜之前几次没好好珍惜考试的机会,拿到200分就撤了
cpp
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
#define pii pair<int,int>
const int NN=1e5+5;
int n,N,q, nowtime; //时间戳
map<int,int>tim; //时间戳
map<int,int>change; //被修改过(替换的话要先写入内存)
set<int>cache[NN]; //NN:组数 //这种i组取set,不用map反而比较方便,用数组 O(1)
set<pii>lru[NN]; //(时间戳 ,当前内存块号pos)
void pop(int id)
{
auto it=lru[id].begin();
int pos=it->second;
if(change[pos])
{
cout<<1<<" "<<pos<<endl;
change[pos]=0;
}
cache[id].erase(pos); //WARN:脏位设为0
lru[id].erase(it); //warn:lru[id] not lru
}
void insert(int id,int pos)
{
cout<<0<<" "<<pos<<endl;
cache[id].insert(pos);
}
void solve()
{
cin>>n>>N>>q;
while(q--)
{
nowtime++; //最近的查询最大,时间戳越大
int o,pos; cin>>o>>pos;
if(o==1) change[pos]=1;
int id=(pos/n)%N; //组的id
if(cache[id].count(pos)) //在缓存区
lru[id].erase(lru[id].find({tim[pos],pos}));
else // 不在缓存区
{
if(cache[id].size()==n) pop(id);
insert(id,pos);
}
//查询完之后更新访问时间
tim[pos]=nowtime;
lru[id].insert({tim[pos], pos});
}
}
int main()
{
ios::sync_with_stdio(0), cin.tie(0);
solve();
return 0;
}