并查集(非连通性问题)——# P2391 白雪皑皑

P2391 白雪皑皑

题目背景

"柴门闻犬吠,风雪夜归人",冬天,不期而至。千里冰封,万里雪飘。空中刮起了鸭毛大雪。雪花纷纷,降落人间。 美能量星球(pty 在 spore 上的一个殖民地)上的人们被这美景所震撼。但是 pty 却不高兴,他不喜欢白色的世界,他觉得这样太单调了。所以他想对雪花进行染色,让世界变得多彩些。

题目描述

现在有 nnn 片雪花排成一列。 pty 要对雪花进行 mmm 次染色操作,第 iii 次染色操作中,把第 ((i×p+q) mod n)+1((i\times p+q)\bmod n)+1((i×p+q)modn)+1 片雪花和第 ((i×q+p) mod n)+1((i\times q+p)\bmod n)+1((i×q+p)modn)+1 片雪花之间的雪花(包括端点)染成颜色 iii。其中 p,qp,qp,q 是给定的两个正整数。他想知道最后 nnn 片雪花被染成了什么颜色。没有被染色输出 000。

输入格式

输入共四行,每行一个整数,分别为 n,m,p,qn,m,p,qn,m,p,q,意义如题中所述。

输出格式

输出共 nnn 行,每行一个整数,第 iii 行表示第 iii 片雪花的颜色。

输入输出样例 #1

输入 #1

复制代码
4
3
2
4

输出 #1

复制代码
2
2
3
0

说明/提示

  • 对于 20%20\%20% 的数据满足:n,m≤1000n,m\leq 1000n,m≤1000。
  • 对于 40%40\%40% 的数据满足:n≤8000n\leq 8000n≤8000,m≤106m\leq 10^6m≤106。
  • 对于 80%80\%80% 的数据满足:n≤5×105n\leq 5\times 10^5n≤5×105,m≤107m\leq 10^7m≤107。
  • 对于 100%100\%100% 的数据满足:1≤n≤1061\leq n\leq 10^61≤n≤106,1≤m≤1071\leq m\leq 10^71≤m≤107。

保证 1≤m×p+q,m×q+p≤2×1091\leq m\times p+q,m\times q+p\leq 2\times 10^91≤m×p+q,m×q+p≤2×109。

代码

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

const int MAXN = 1e6 + 5;  // 常量名更语义化
struct Snowflake {
    int color;  // 雪花最终染色编号(0表示未染色)
    int next;   // 以当前位置为起点,首个未染色的位置(路径压缩用)
} snow[MAXN];

// 并查集查找:找到x位置开始的首个未染色位置,路径压缩优化
int find(int x) {
    if (snow[x].next != x) {
        snow[x].next = find(snow[x].next);  // 路径压缩,直接指向最终未染色位置
    }
    return snow[x].next;
}

int main() {
    ios::sync_with_stdio(false);  // 加速cin/cout,应对1e6量级数据
    cin.tie(nullptr);

    int n, m, p, q;  // n:雪花总数 m:染色操作数 p/q:随机数参数
    cin >> n >> m >> p >> q;

    // 初始化:所有雪花未染色,每个位置的首个未染色位置是自身;n+1作为边界哨兵
    for (int i = 1; i <= n + 1; ++i) {
        snow[i].color = 0;
        snow[i].next = i;
    }

    // 逆序处理染色操作:后执行的操作覆盖先执行的,保证每个位置只染最后一次
    for (int op = m; op >= 1; --op) {
        // 计算当前操作的染色区间 [l, r],保证1<=l<=r<=n
        int pos1 = ((1LL * op * p + q) % n) + 1;  // 1LL防止乘法溢出
        int pos2 = ((1LL * op * q + p) % n) + 1;
        int l = min(pos1, pos2);
        int r = max(pos1, pos2);

        // 遍历区间内所有未染色位置:通过find直接跳转到未染色位置,避免重复遍历
        for (int j = find(l); j <= r; j = find(j)) {
            snow[j].color = op;    // 标记为当前操作的颜色(逆序保证是最后一次染色)
            snow[j].next = j + 1;  // 染完后,该位置的首个未染色位置指向右侧
        }
    }

    // 输出每个雪花的最终颜色
    for (int i = 1; i <= n; ++i) {
        cout << snow[i].color << '\n';  // '\n'比endl快,避免刷新缓冲区
    }

    return 0;
}

总结

并查集「非连通性问题」的经典应用(非常规的合并 / 查找,而是路径压缩的灵活复用),解决了大量区间覆盖 / 跳过已处理元素的问题。

  1. 用并查集的「路径压缩」特性跳过已处理区间,将区间染色的时间复杂度从暴力O(mn) 优化到O(nα(n))(α(n) 是阿克曼反函数,近乎常数);
  2. 逆序处理覆盖类操作,保证每个位置只处理最后一次有效操作,避免重复计算;
  3. 初始化到n+1 作为「哨兵位置」,避免处理最后一个位置时的越界判断,简化逻辑。
相关推荐
Darkwanderor15 天前
数据结构 - 并查集的应用
数据结构·c++·并查集
老鼠只爱大米1 个月前
LeetCode经典算法面试题 #236:二叉树的最近公共祖先(RMQ转化、Tarjan离线算法等五种实现方案详细解析)
算法·leetcode·二叉树·lca·并查集·最近公共祖先·rmq
轩情吖1 个月前
数据结构-并查集
开发语言·数据结构·c++·后端··并查集
adam_life2 个月前
并查集+树高【P1196 [NOI2002] 银河英雄传说】
递归·并查集·寻根同时更新路径上父节点的根
老鼠只爱大米2 个月前
LeetCode算法题详解 56:合并区间
leetcode·并查集·合并区间·区间合并·线性扫描·算法面试
罗湖老棍子2 个月前
团伙(group)(信息学奥赛一本通- P1385)
算法·图论·并查集
罗湖老棍子2 个月前
【例4-8】格子游戏(信息学奥赛一本通- P1347)
stl·图论·并查集·二维坐标压缩
罗湖老棍子2 个月前
【模板】并查集(洛谷P3367)
算法·图论·并查集
老鼠只爱大米2 个月前
LeetCode算法题详解 128:最长连续序列
算法·leetcode·面试题·并查集·哈希集合·最长连续序列