agc036B Do Not Duplicate

题意:给一个长度为$n$的序列$a_i$和一个数$k$。

现在把$a$序列重复$k$次生成一个数列$b$,然后从第一位开始往一个栈中加入$b_i$ 。

如果这个栈里存在元素与$b_i$相等那么一直pop直到把那个数弹掉然后不加入新数;否则push$b_i$入栈。求全部操作完之后栈中的元素。

$n \leq 2 \times 10^5, k \leq 10^{12}, a_i \leq 2 \times 10^5$

 

循环节。Yyyyyyyk都知道是循环节。

如果当前栈内第一个元素为$x$,则下一次栈空即为下一次遇到$x$。

所以预处理出每个$a_i$下一个和它相同的位置,假设为$j$,将$i$向$j+1$连边,即下次栈首元素为$a[j+1]$。

可以证明最后连出的一定是若干个环而不是基环内向树,因为每个点有唯一出边且有唯一入边。

也就是说1一定可以走回1,一定存在循环节,环长度最大是n,而对于k来说,循环节长度最大为n+1

所以在环上暴力跑出循环节,将k%循环节长度后,剩下的在环上暴力跑,多出来的一点也暴力跑就好

复杂度$O(n)$

场上想到了循环节,也想到了预处理nxt之类的,但是一直在倒着想,然后就想不出来。

 

#include<bits/stdc++.h>
using namespace std;
const int maxn = 2e5 + 7;
long long n, k, d;
int a[maxn], lst[maxn], to[maxn], zz[maxn], t, hs[maxn];

int main() {
	scanf("%lld%lld", &n, &k);
	for (int i = 1; i <= n; ++i) scanf("%d", &a[i]);
	for (int i = n; i; --i) lst[a[i]] = i;
	for (int i = n; i; --i) {
		to[i] = lst[a[i]];
		lst[a[i]] = i;
	}
	int pos = 1;
	do {
		if(to[pos] <= pos) ++d;
		if(to[pos] == n) ++d;
		pos = to[pos] % n + 1;
	}while(pos != 1);
	k %= d;
	if(k == 0) return 0;
	pos = 1;
	while(1) {
		if(to[pos] <= pos) {
			if(k > 1) --k;
			else break;
		}
		pos = to[pos] % n + 1;
	}
	for (int i = pos; i <= n; ++i) {
		if(hs[a[i]]) {
			//cerr << a[i] << endl;
			while(zz[t] != a[i]) 
				hs[zz[t--]] = 0;
			hs[zz[t--]] = 0;
		}
		else {
			hs[a[i]] = 1;
			zz[++t] = a[i];
		}
	}
	for (int i = 1; i <= t; ++i) printf("%d ", zz[i]);
	printf("\n");
	return 0;
}

  

posted @ 2020-07-28 23:38  shixinyi  阅读(231)  评论(0编辑  收藏  举报