【题解】白雪皑皑

并查集这个东西的结构还是很简单的。

P2391 白雪皑皑

\(link\)

对于题目,大多数人肯定会想到线段树,但是这道题的 \(m\) 太大了,以 \(Seg\) \(tree\)\(O(mlog_n^2)\) 的复杂度绝对会 T 的。

所以这道题可谓是一道并查集绝世好题啊。

并查集是用来干啥的呢,你先别急,后头再说。

我们首先将每个点的 \(fa\) 节点的祖先连向他自己。

img

然后从后向前遍历这个序列。

特注:为什莫要从后向前遍历这个序列呢?因为如果从前向后遍历的话后面一定有操作将前面操作的数覆盖,这样前面操作的就没用了。但如果从后向前遍历这个序列的话,那么只要在确定这个节点已经被覆盖后,就不用再次覆盖这个节点了,可见这是一个在覆盖问题中常见的优化技巧。

现在可以说为什莫要用并查集和并查集是干啥的了。并查集里存的祖先是啥呢,是在这个序列前面第一个是 \(0\) 的节点。

当有操作第一次覆盖这个节点时,我们将这个节点覆盖,然后将这个节点的 \(fa\) 指向 \(l+1\),或许你会觉得 \(l+1\) 也有可能不是 \(0\),但这没有丝毫的关系,因为在下一步,我们会路径压缩啊!之后便历下去就好了。

img

详见 \(Code\)

#include<bits/stdc++.h>
using namespace std;
typedef int ll;
const ll N=1e6+5;
ll fa[N],a[N];
ll n,m,p,q;
ll fd(ll x){
	if(fa[x]==x)return x;
	return fa[x]=fd(fa[x]);
}
int main(){
	cin>>n>>m>>p>>q;
	for(ll i=1;i<=n+1;i++)fa[i]=i;
	for(ll i=m;i>=1;i--){
		ll l=(i*p+q)%n+1;
		ll r=(i*q+p)%n+1;
		if(l>r)swap(l,r);
		l=fd(l);
		while(l<=r){
			a[l]=i;
			fa[l]=l+1;
			l=fd(l+1);
		}
	}
	for(ll i=1;i<=n;i++){
		cout<<a[i]<<"\n";
	}
	return 0;
}

再次警示后人一下:for(ll i=1;i<=n+1;i++)fa[i]=i;,在这一句话中,我们通常是赋值到 \(n\),这里为什么是 \(n+1\) 呢,因为当你把 \(l\)\(fa\) 数组指向 \(l+1\) 时,思考一下,当你的 \(l\) 如果等于 \(n\) 呢,他会指向 \(fa_{n+1}\),即为 \(0\),细细思考一下,会发现这十分的危险,因为这样会使你步入死循环。

posted @ 2025-08-06 23:12  Kcjhfqr  阅读(8)  评论(0)    收藏  举报
.poem-wrap { position: relative; width: 1000px; max-width: 80%; border: 2px solid #797979; border-top: none; text-align: center; margin: 40px auto; } .poem-left { left: 0; } .poem-right { right: 0; } .poem-border { position: absolute; height: 2px; width: 27%; background-color: #797979; } .poem-wrap p { width: 70%; margin: auto; line-height: 30px; color: #797979; } .poem-wrap h1 { position: relative; margin-top: -20px; display: inline-block; letter-spacing: 4px; color: #797979; font-size: 2em; margin-bottom: 20px; } #poem_sentence { font-size: 25px; } #poem_info { font-size: 15px; margin: 15px auto; }