LRU算法是指对于一个不断增长的数字序列 a i a_i ai和一个窗口大小 m m m,用窗口按出现顺序维护最新出现的 m m m个不同元素。
多组数据,现在给一个长为 n ( 5000 ) n(5000) n(5000)的数字序列,以及 q ( 2000 ) q(2000) q(2000)次询问。
每次询问给定一个 m i m_i mi和长为 m i m_i mi的队列,问如果对大小为 m m m的窗口执行LRU算法,是否有一个时刻窗口和给定的队列相等。
在容量无限的窗口中对给定序列执行LRU算法,把每个时刻的窗口记录下来,并记录所有前缀哈希值。就相当于得到了对所有窗口大小的哈希值。
对于每个询问,在这个窗口大小的所有历史哈希值中判断待查询队列的哈希值是否出现过。
如果没有一个哈希值与它相等,就证明没有出现过。
如果存在一个哈希值与它相等,为了防止哈希冲突,可以使用双哈希或者保存序列二次判断。
第一次写二次判断哈希,感觉要比双哈希靠谱一些,不过复杂度不好保证。
/* LittleFall : Hello! */
#include <bits/stdc++.h>
using namespace std; using ll = long long; inline int read();
const int M = 5016, MOD = 1000000007;
int toque[M];
int que[M][M];
ll LRU_hash[M][M];
int main(void)
{
#ifdef _LITTLEFALL_
freopen("in.txt","r",stdin);
#endif
int T = read();
while(T--)
{
int n = read(), q = read();
memset(que[0], 0, (n+1)*sizeof(que[0][0]));
for(int i=1, qlen=0; i<=n; ++i)
{
memcpy(que[i], que[i-1], (n+1)*sizeof(que[0][0]));
int now = read(), pos = 0;
for(int j=1; j<=qlen; ++j)
if(que[i][j]==now)
pos = j, j = qlen+1;
for(int j=(pos?pos:++qlen); j; --j)
que[i][j] = que[i][j-1];
que[i][1] = now;
for(int j=1; j<=n; ++j)
LRU_hash[i][j] = (LRU_hash[i][j-1]*(n+1)+que[i][j])%MOD;
}
while(q--)
{
int m = read();
ll hashv = 0;
for(int i=1; i<=m; ++i)
{
toque[i] = read();
hashv = (hashv*(n+1)+toque[i])%MOD;
}
int suc = 0;
for(int i=0; i<=n; ++i) if(LRU_hash[i][m]==hashv)
{
int flag = 1;
for(int j=1; j<=m; ++j)
if(que[i][j]!=toque[j])
flag = 0, j = m+1;
if(flag)
suc = 1, i=n+1;
}
printf("%s\n",suc?"Yes":"No" );
}
}
return 0;
}
inline int read(){
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9') {if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}