[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

题面

CodeForces-1225B.png

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
", minn);
	}
	return 0;
}



原文地址:https://www.cnblogs.com/satchelpp/p/12068814.html