C. MEX Repetition

题目:C. MEX Repetition_第1张图片样例:

输入
5
1 2
1
3 1
0 1 3
2 2
0 2
5 5
1 2 3 4 5
10 100
5 3 0 4 2 1 6 9 10 8

输出
1
2 0 1
2 1
2 3 4 5 0
7 5 3 0 4 2 1 6 9 10

C. MEX Repetition_第2张图片

思路:

        从题目和样例中,我们可以知道,从一个数组中,按照包括0的自然数的顺序查找确实的一个顺序,然后按从左到右进行交换。想法和思路弄清楚后,暴力模拟肯定是不行的了,数据范围过大,k的数据范围是 1e9, n 的数据范围是 1e5,在加上 t 的数据范围 1e5,如果纯暴力,按照最坏的情况,时间复杂度大概是 10 的19次方。所以基本不可能的。

        我们可以看一下样例,可以发现一个规律,那就是第一次缺少的包括0的自然数 x ,在输出样例中,会去掉前面或者后面,然后将该 x 放置前面或者后面。我们可以猜测,并可以知道,某次交换后,可能会和前面少的一次效果很可能相同,即根据长度 n ,k % n + 1次操作 和 k 次操作效果相同,这里  k % n + 1 是当 k % n 刚好整除的时候,k 次操作应该有效的,即至少有一次有效操作,所以  k % n + 1,可以缩短了 k 的数据范围,并且缩短了时间复杂度,其次该 x 不是在前就是在后,并且中间基本顺序不会变,我们又可以猜测,并且有点接近了前后队列的操作性质,估计是 双端队列 的操作。

        即 k % n + 1 次  的将后端放置前端,然后按照原数组个数,顺序输出队列。

代码详解如下:

#include 
#include 
#include 
#include 
#include 
#include 
#define endl '\n'
#define YES puts("YES")
#define NO puts("NO")
#define umap unordered_map
#define All(x) x.begin(),x.end()
#pragma GCC optimize(3,"Ofast","inline")
#define ___G std::ios::sync_with_stdio(false),cin.tie(0), cout.tie(0)
using namespace std;
const int N = 2e6 + 10;

inline void solve()
{
	int n,k,x = 0;
	cin >> n >> k;
	
	// 存储双端队列
	dequeq;
	
	// 标记数组中的包括0自然数是否出现过
	umapr;
	
	for(int i = 0,num;i < n;++i)
	{
		cin >> num;
		
		// 按照顺序存储好原数组
		q.push_back(num);
		
		// 标记好出现过的包括0自然数
		r[num] = true;
		
		// 寻找按照顺序没出现过的包括0自然数
		while(r[x]) ++x;
	}
	
	// 存储好 x 在尾端,然后根据 k 进行放前端
	q.push_back(x);
	
	// k 次操作 与 k %= n + 1 次操作效果相同
	// 有效缩短时间复杂度
	k %= n + 1;
	
	// k 次操作 队列,将尾端放置前端
	while(k--)
	{
		q.push_front(q.back());
		q.pop_back();
	}
	
	// 按照原个数,顺序输出操作完后的数组
	while(n--)
	{
		cout << q.front() << ' ';
		q.pop_front();
	}
	cout << endl;
}
int main()
{
//	freopen("a.txt", "r", stdin);
	___G;
	int _t = 1;
	cin >> _t;
	while (_t--)
	{
		solve();
	}
	return 0;
}

最后提交:

你可能感兴趣的:(算法错题本,玩转上号CF“游戏”,算法)