给出一个LRU算法的操作序列,q个查询,询问在内存池大小为m时,LRU操作过程中会不会出现查询给出的内存池状态。
我们发现内存池大小的限制因为LRU算法的特殊性质,仅仅只是把当前状态限制成了无限内存池大小的一个前缀。
这题就变成了一个前缀匹配问题。由于n比较小,可以考虑n方的算法,模拟LRU算法的过程,不加内存限制,对于每一个中间状态,都去跑一遍查询建立的字典树,在字典树上经过的前缀显然就是答案Yes。
要不是打了多校我还真不太会模拟LRU,但是牛客多校出了一题模拟LRU,题解给出了一种迭代器数组记录list链表每一种数字迭代器位置,O(n)且快乐简短的做法(当时写了一个nlogn的也过了,不过贼恶心),再做这题就很愉快了,短短几行就写完了。
踩过的坑:
查询有1 2 3 0 0 0这种后缀带0的,代表着此时内存池没有满,有三个空位,这种时候仍然把0加入字典树,并且在跑出的LRU状态在字典树上跑完之后,如果后面还有0的转移,就无脑全部跑完。
由于字符集过大,使用unordered_map作为转移。
ac代码:
#include<bits/stdc++.h>
using namespace std;
const int maxn = 2e6 + 5;
int _, n, q, a[5005], b[5005], m[2005], last[2005];
struct Trie {
unordered_map<int, int> next[maxn];
bool mark[maxn];
int sz, root;
void init() {
sz = 0;
root = newNode();
}
int newNode() {
++sz;
next[sz].clear();
mark[sz] = 0;
return sz;
}
int add(int len) {
int p = root;
for (int i = 0; i < len; ++i) {
if (!next[p].count(b[i])) {
next[p][b[i]] = newNode();
}
p = next[p][b[i]];
}
return p;
}
} trie;
list<int>::iterator mp[5005];
list<int> l;
void solve() {
l.clear();
for (int i = 0; i < n; ++i) {
mp[a[i]] = l.end();
}
for (int i = 0; i < n; ++i) {
if (mp[a[i]] != l.end()) {
l.erase(mp[a[i]]);
}
l.push_front(a[i]);
mp[a[i]] = l.begin();
int p = trie.root;
for (auto e:l) {
if (!trie.next[p].count(e)) {
break;
}
p = trie.next[p][e];
trie.mark[p] = 1;
}
while(trie.next[p].count(0)){
p = trie.next[p][0];
trie.mark[p] = 1;
}
}
for (int i = 0; i < q; ++i) {
if (trie.mark[last[i]]) {
printf("Yes\n");
} else {
printf("No\n");
}
}
}
int main() {
scanf("%d", &_);
while (_--) {
trie.init();
scanf("%d%d", &n, &q);
for (int i = 0; i < n; ++i) {
scanf("%d", &a[i]);
}
for (int i = 0; i < q; ++i) {
scanf("%d", &m[i]);
for (int j = 0; j < m[i]; ++j) {
scanf("%d", &b[j]);
}
last[i] = trie.add(m[i]);
}
solve();
}
return 0;
}
/*
1
7 5
4 3 4 2 3 1 4
3 3 2 1
2 2 3
3 3 2 1
4 4 1 3 2
4 3 4 0 0
*/