HDU-2158 最短区间版大家来找茬 模拟 尺取
HDU-2158 最短区间版大家来找茬 模拟 尺取
题意
给定一个长度为\(N\) 的序列,序列中每个整数的范围是\([0,N)\)
给出\(M\) 个询问,每次询问给出一个整数\(Q\) ,接下来有\(Q\) 个整数,这\(Q\) 个整数可能包含重复值。
现要找出一个最短的区间包含这\(Q\) 个整数。输出最小的区间长度
\[N < 100000,M < 1000,Q < 100
\]
分析
显然是个模拟题。
暴力必然是不可取的,考虑每个询问O(N)的做法,那可以刚刚好过掉这题。
方法是尺取,相当于给定一个标杆\(l\) 和\(r\) ,从左到右枚举\(l\) ,对于每个\(l\) ,\(r\)必须满足条件为止,满足条件后维护最小值。
思路想起来容易实现起来难:
1.如何判断是否满足了条件
2.如何在移动的时候维护信息
由于题目的特殊性质,序列可以是不同的数,因此可以建立一个\(vis\) 数组和\(Find\) 分别标记这个数是否是要找的数,以及这个要找的数已经在区间内的次数。
代码
int n, m;
int vis[100005];
int Find[100005];
int a[100005];
int main() {
while (scanf("%d%d", &n, &m), n && m) {
for (int i = 0; i < n; i++) a[i] = readint();
while (m--) {
for (int i = 0; i < n; i++) vis[i] = 0, Find[i] = 0;
int q = readint();
int cnt = 0;
int ans = n;
int x;
for (int i = 0; i < q; i++) {
x = readint();
if (!vis[x]) cnt++;
vis[x]++;
}
q = cnt;
for (int i = 0; i < q; i++) if (vis[a[i]] && !Find[a[i]]) cnt--,Find[a[i]]++; else if (vis[a[i]]) Find[a[i]]++;
if (!cnt) ans = q;
int l = 0, r = q ;
for (int l = 0; l + q <= n; l++) {
while (cnt && r < n) {
if (vis[a[r++]] && !Find[a[r - 1]]) cnt--,Find[a[r - 1]]++; else if (vis[a[r - 1]]) Find[a[r - 1]]++;
}
if (!cnt) ans = min(ans, r - l);
if (Find[a[l]]) {
Find[a[l]]--;
if (!Find[a[l]]) cnt++;
}
}
printf("%d\n", ans);
}
}
}