大体思路:
我们把前m个数放到缓存区后,再碰到不在缓存区里的数时,我们需要删除一个原来在缓存区的数,然后把这个数加到缓存区内,首先问题在于删除那个数,我首先想到删除后面出现少的那个数,但这不对,反例如下:
1000 3
12 15 16 14 12 15 14 12 15 后面的数全是16
对于这个样例,如果对于14这个数,用上面的思路一定不删16,而实际上删16最划算,因为你不管删12还是15,在后面的几个数中你都要频繁的删数,而删16的话,紧跟着后面的这几个数不需要删除,后面的一坨16也只需要处理一次即可。
正确的贪心是,把缓存区中元素中下一次出现最晚的那个给删除了,所以我们需要一个数组来存第i个数的下一次出现在哪里,要一直找最晚的我们需要一个堆等等。
#include <bits/stdc++.h>
using namespace std;
int n, m;
//这个map是基于当前位置i
// 从n到1找ar[i]最后一次出现的位置(注意这个顺序n ~ 1)
// 即基于当前位置向后找第一次出现ar[i]的位置
map<int,int> mp;
bool boo[100050];//表示第i个数在不在缓存区内
int nexti[100050];//存第i个数下一次出现的位置
int ar[100050];
priority_queue<int> q;//用于存储缓存区内的元素
int num, ans;// num当前缓存区内元素个数,ans答案
int main()
{
//n,主存访问次数,m容量
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; ++i)
{
scanf("%d", &ar[i]);
}
// 从后向前遍历主存访问请求,找到每个元素最后一次出现的位置
for(int i = n; i > 0; --i)
{
//对于每个元素,检查它是否在map mp中
// 如果不在,说明这是该元素第一次出现,将nexti[i]设置为一个大值
//表示该元素后面没有再次出现。
if(mp[ar[i]] == 0)
nexti[i] = 100050;
//如果在,说明该元素之前已经出现过
// 将nexti[i]设置为该元素最后一次出现的位置,即mp[ar[i]]。
else nexti[i] = mp[ar[i]];
//更新map mp,将当前元素的索引i作为键,元素的值ar[i]作为值。
mp[ar[i]] = i;
}
// 遍历主存访问请求
for(int i = 1; i <= n; ++i)
{
if(boo[i] == 0)// 如果当前元素不在缓存区内
{
ans++; // 缺失次数加1
if(num < m)// 如果缓存区未满
{
num++; // 缓存区内元素个数加1
boo[nexti[i]] = 1; // 将当前元素标记为在缓存区内
q.push(nexti[i]); // 将当前元素加入优先队列
}
else// 如果缓存区已满
{
boo[q.top()] = 0;// 移除缓存区内最久未使用的元素
q.pop();
boo[nexti[i]] = 1;// 将当前元素标记为在缓存区内
q.push(nexti[i]);// 将当前元素加入优先队列
}
}
else// 如果当前元素已经在缓存区内
{
boo[nexti[i]] = 1;// 将当前元素标记为在缓存区内
q.push(nexti[i]);// 将当前元素加入优先队列
}
}
printf("%d\n", ans);
return 0;
}