[CodeForces-1225B] TV Subscriptions 【贪心】【尺取法】
[CodeForces-1225B] TV Subscriptions 【贪心】【尺取法】
标签: 题解 codeforces题解 尺取法
题目描述
Time limit
2000 ms
Memory limit
262144 kB
Source
Technocup 2020 - Elimination Round 2
Tags
implementation two pointers *1300
Site
https://codeforces.com/problemset/problem/1225/B2
题面
Example
Input
4
5 2 2
1 2 1 2 1
9 3 3
3 3 3 2 2 2 1 1 1
4 10 4
10 8 6 4
16 9 8
3 1 4 1 5 9 2 6 5 3 5 8 9 7 9 3
Output
2
1
4
5
题目大意
给定\(n, k, d\)和序列\(a[1 \cdots n]\),\(1 \leq a[i] \leq k\)。问序列中连续\(d\)个数最少能有几个不同的数?
例如,
\(n = 5, k = 2, d = 2, a[1 \cdots 5] = [1, 2, 1, 2, 1]\)。连续的两个数只有\([1, 2], [2, 1]\)两种情况都是包含2个不同的数。所以输出2;
\(n = 9, k = 3, d = 3, a[1 \cdots 9] = [3, 3, 3, 2, 2, 2, 1, 1, 1]\)。当连续的三个数为\([3, 3, 3]\)时,包含的不同数的个数最少,为1个。若为其他,如\([2, 2, 1]\)或\([3, 2, 2]\),则包含2个不同的数。所以输出1。
解析
这道题用到了尺取法,尺取法在Codeforces中的标签是two pointers(双指针法)。
但因为题目中规定了\(d\),即尺取的长度,两个指针只能一起移动,所以尺取的思想体现的不是很明显。
既然尺取的左右两个指针\(l, r\)如何移动的问题已经解决,那么接下来要解决的就是怎么维护尺取区间的信息?这道题是询问区间所包含的不同数字数最小。我们通过观察发现,每次\(l, r\)向右移动一位其实区间中只有两个变化,一个是先前\(l\)所指的数字出队,另一个是当前\(r\)所指的数字入队。那么我们只需查看这两个数字的一出一入对队列中不同数字数产不产生影响就可以了。这样我们就通过数组\(vis[1 \cdots k]\)来记录尺取区间中每个数字出现的次数。
- 如果\((++ vis[a[r]]) = 1\),那么不同数字数就加一。
- 如果\((-- vis[a[l - 1]]) = 0\),那么不同数字数就减一。
这样尺取区间从最左端滑到最右端,记录下不同数字数的最大值即是答案。该方法的时间复杂度是\(O(n)\)。
因为有多组输入,所以我们每次都要给数组\(vis\)归零。注意,数组归零的时候,如果使用k,循环k次会TLE。
通过代码
/*
Status
Accepted
Time
31ms
Memory
7836kB
Length
937
Lang
GNU G++11 5.1.0
Submitted
2019-12-17 21:52:46
RemoteRunId
67074221
*/
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1e6 + 50;
int vis[MAXN], a[MAXN], n, k, d;
inline int read() //快读,因为是2e5的输入量,所以加快读能明显提高程序速度.
{
int res = 0, f = 1;
char ch;
ch = getchar();
while(!isdigit(ch)){
if(ch == '-')
f = -1;
ch = getchar();
}
while(isdigit(ch)){
res = (res << 3) + (res << 1) + ch - 48;
ch = getchar();
}
return f * res;
}
void mem0() //vis数组归零函数.
{
for(int i = 1; i <= n; i ++) //注意这里要循环n次而不是k次,否则会超时.
vis[a[i]] = 0;
return ;
}
int main()
{
int times;
times = read();
while(times --){
int minn = 0, cnt = 0;
mem0();
n = read(), k = read(), d = read();
for(int i = 1; i <= n; i ++){
a[i] = read();
}
for(int i = 1; i <= d; i ++){ //先将最左端的d个数加入区间,构造好尺取区间.
if(!vis[a[i]]) cnt ++;
vis[a[i]] ++;
}
minn = cnt;
for(int i = d + 1; i <= n; i ++){
if(!vis[a[i]]) cnt ++; //注意这里并没有真的使用l,r指针(下标),因为l,r的移动是同时的,所以我们就用循环的i代替r,i-d代替l-1.
vis[a[i]] ++;
vis[a[i - d]] --;
if(!vis[a[i - d]]) cnt --;
minn = min(minn, cnt); //每次移动一位之后,看是否可以更新最小值.
}
printf("%d\n", minn);
}
return 0;
}