P2391 白雪皑皑

是一个比较 dirty 的题。

题意很简单。那么首先就可以想到线段树的区间推平。

注意到 \(n \le 10^7\),那么这个对常数要求很严格。

于是,我们的算法必须是 \(\mathcal{O}(m)\),或者 $ n \log n$ 的。

接着,我们发现对于 \(i\),我们想求的其实是 \(i\) 最后被哪个区间覆盖。

那么,我们倒过来找 \(i\) 第一个被哪个区间覆盖就行了。

那么考虑维护一个链表状物,\(nxt[i]\) 代表 \([i, n]\) 中间,第一个没被染色的点。

每次查询的时候我们更新。

显然更新的过程中,我们可以,也必须使用路径压缩。

分析一下时间复杂度:这相当于一个并查集,被合并 \(n - 1\) 次,那么复杂度是 \(\mathcal{O}(n \log n + m)\),比线段树的要优秀。

// Problem: P2391 白雪皑皑
// Contest: Luogu
// Memory Limit: 512 MB
// Time Limit: 1000 ms

#include <bits/stdc++.h>
#define Misaka namespace
#define Network std
using Misaka Network;
const int N = 1e6 + 7, M = 1e7 + 7;
int nxt[N], n, m, p, q, col[N];

int find(int x){return (nxt[x] == x ? x : nxt[x] = find(nxt[x]));}

signed main(){
	ios::sync_with_stdio(0), cin.tie(0);
	
	cin >> n >> m >> p >> q;
	for(int i = 1; i <= n + 1; i ++) nxt[i] = i;
	
	for(int i = m; i >= 1; i --){
		int l = (i * p + q) % n + 1;
		int r = (i * q + p) % n + 1;
		if(l > r) swap(l, r);
		
		for(int j = find(l); j <= r; j = find(j)){
			col[j] = i, nxt[j] = j + 1;
		}
		
	}
	
	for(int i = 1; i <= n; i ++) cout << col[i] << "\n";
	
	return 0;
}
posted @ 2026-07-16 13:44  Trent900  阅读(12)  评论(0)    收藏  举报