每个测试用例的第一行包含三个整数 n 、 m 和 k ( 1≤k≤m≤n≤2⋅105 )–数组 a 和 b 中的元素个数,也就是所需的匹配元素个数。
每个测试用例的第二行包含 n 个整数 a1,a2,…,an ( 1≤ai≤106 )。( 1≤ai≤106 ) - 数组 a 的元素。数组 a 中的元素不一定是唯一的。
每个测试用例的第三行包含 m 个整数 b1,b2,…,bm ( 1≤bi≤106 ) - 数组 b 的元素。数组 b 中的元素不一定是唯一的。
保证所有测试用例中 n 的总和不超过 2⋅105 。同样,保证所有测试用例中 m 的总和不超过 2⋅105 。
输出描述
对于每个测试用例,另起一行输出数组 a 中良好子段的数量。
示例
输入
text
1 2 3 4 5 6
5 1 1 1 0 1 0 1 2 2 2 2 0 3 3 2 0 0 9 9 9
输出
text
1 2 3 4 5
1 1 3 3 12
说明
在第一个例子中,所有分段都很好。
在第二个示例中,好的子线段从位置 1 、 2 和 3 开始。
在第三个示例中,好的子线段从位置 1 和 2 开始。
题解
按照题目意思,我们需要从左到右依次从 a 中取与 b 等长的数组来判断这个数组是否为 “好线段” 。这道题的关键点在于怎么计算一个数组与 b 数组的相同个数。,显然,每次都对取出的数组进行计数时间开销过大。所以我们选择额外开一个数组,这个数组的下标即为 b 中元素的值,我们在输入 b 的值的时候就将 b 的值记入下来,每出现一个值就将对应的下标的值加 1 。
我们再来思考一个问题,如果 a 数组为 [1,1,1,1] ,b数组为 [1,2,3] ,我们要求的 k 为 3 ,这个时候按我们之前的设想得出的答案是 2 ,显然这个答案是错误的。这个时候我们需要再用一个数组来维护当前数组每个数字出现的次数,并于之前的进行比较,得出最后答案。
至于怎么从 a 数组 中取与 b 数组等长的子数组,当然是使用双指针了,这里其实还有另外一个叫法:“滑动窗口”。
voidsolve() { map<int, int> c; // 用于判断a是否在b中出现 map<int, int> d; // 用于判断b中某个数的个数是否超过限制 int n, m, k; cin >> n >> m >> k; for (int i = 0; i < n; i++) { cin >> a[i]; } for (int i = 0; i < m; i++) { cin >> b[i]; c[b[i]]++; }
int ans = 0; int cnt = 0; // 初始窗口 for (int i = 0; i < m; i++) { if (c[a[i]] && d[a[i]] < c[a[i]]) { ans++; d[a[i]]++; } elseif (c[a[i]]) { d[a[i]]++; } } if (ans >= k) { cnt++; } // 滑动窗口 for (int i = m; i < n; i++) { // 前窗口部分 if (c[a[i - m]] && d[a[i - m]] <= c[a[i - m]]) { ans--; d[a[i - m]]--; } elseif (c[a[i - m]]) { d[a[i - m]]--; } // 后窗口部分 if (c[a[i]] && d[a[i]] < c[a[i]]) { ans++; d[a[i]]++; } elseif (c[a[i]]) { d[a[i]]++; } if (ans >= k) { cnt++; } } cout << cnt << endl; return; }