【并查集】判环

题意梳理

  1. (n* n) 棋盘,m 个车,初始无两车同行同列;
  2. 单次操作:移动一个车任意横竖距离,移动后仍不能同行同列;
  3. 目标:让所有车放到主对角线 (i,i),求最小移动次数;
  4. 关键:(m<n),棋盘存在空行空列。

Problem - 1411C - Codeforces

一、为什么要把题目转换成图结构

  1. 原始棋盘限制 所有车不会同行、同列,也就是说每一个列数字只出现一次,每一个行数字只出现一次。 把列编号、行编号全部当成普通数字点,一辆车坐标 (x,y),就代表数字 x 和数字 y 存在绑定关系。

  2. 连通块等价含义 如果两个数字在同一个连通块里,代表块内所有行列上的车,依靠题目允许的移动规则,可以自由调换位置,没有永久冲突。 我们最终目标是让数字 k 的列匹配数字 k 的行,也就是位置 (k,k)。

  3. 两种连通块带来不同代价

  • 链型块:块里点的数量 > 块里车的数量 没有互相卡死的情况,每一辆不在对角线上的车,只需要移动一次就能归位,没有额外开销。
  • 环型块:块里点的数量 = 块里车的数量 环里所有车辆互相占用对方需要的行 / 列,直接移动一定会冲突。题目保证 m<n,棋盘存在空行列,必须先挪一辆车去空位中转,每一个环额外多消耗一次操作。
  1. 特殊车辆处理 如果一辆车坐标 (x,x),本身就在主对角线上,不需要移动,不参与建图、不计入移动总量。

二、文字符号定义(纯文本,可直接复制)

  1. R:全部车辆集合,每个元素是一对数字 (x,y) cnt:满足 x≠y 的车辆总数量,每一台基础消耗 1 次移动

R(x≠y) = cnt

  1. G:仅由 x≠y 的车辆构建的无向图 S:图 G 里任意一个连通块 |S|:连通块 S 包含的数字点总数 E (S):连通块 S 内部包含的车辆(边)总数

  2. 判断一个连通块是环的条件 E (S) > 0 并且 |S| == E (S)

  3. loop:图 G 中所有满足上面条件的环连通块总个数

  4. 最终答案计算式 ans = cnt + loop

三、并查集数组对应含义

fa u:数字 u 在并查集中的父节点 siz rt:根为 rt 的连通块,包含的点总数,对应 | S| cntE rt:根为 rt 的连通块,包含的有效车辆数量,对应 E (S) vis rt:标记该连通块根是否已经统计过,防止重复计算环

复制代码
#include <cstdio>
using namespace std;
const int MAX = 100010;
int fa[MAX], siz[MAX], cntE[MAX];
bool vis[MAX];

int find(int x) {
    if (fa[x] != x) fa[x] = find(fa[x]);
    return fa[x];
}

int main() {
    int t; scanf("%d", &t);
    while (t--) {
        int n, m; scanf("%d%d", &n, &m);
        for (int i = 1; i <= n; ++i) {
            fa[i] = i, siz[i] = 1, cntE[i] = 0, vis[i] = 0;
        }
        int cnt = 0;
        for (int i = 0; i < m; ++i) {
            int x, y; scanf("%d%d", &x, &y);
            if (x == y) continue;
            cnt++;
            int fx = find(x), fy = find(y);
            if (fx != fy) {
                fa[fy] = fx;
                siz[fx] += siz[fy];
                cntE[fx] += cntE[fy];
            }
            cntE[fx]++;
        }
        int loop = 0;
        for (int i = 1; i <= n; ++i) {
            int rt = find(i);
            if (!vis[rt]) {
                vis[rt] = 1;
                if (cntE[rt] && siz[rt] == cntE[rt]) loop++;
            }
        }
        printf("%d\n", cnt + loop);
    }
    return 0;
}
相关推荐
持力行1 小时前
C/C++ 中的 char*:它标识数组吗?为什么能用下标访问?
c语言·c++
Jerry1 小时前
KeetCode 44. 开发商购买土地
算法
Jerry2 小时前
KeetCode 58. 区间和
算法
Jerry3 小时前
LeetCode 209. 长度最小的子数组
算法
汉克老师3 小时前
GESP2026年6月认证C++六级( 第三部分编程题(2、满二叉树))精讲
c++·深度优先·树形dp·满二叉树·gesp六级·树形dfs
彦为君3 小时前
算法思维与经典智力题
java·前端·redis·算法
智能优化与强化学习3 小时前
Gym(Gymnasium)仿真环境详解(二):环境简介、入门算法、调参要点、核心挑战
算法·强化学习·gym·零基础入门·算法评估
mxwin3 小时前
Unity Shader exp 函数的算法与渲染应用
算法·unity·游戏引擎·shader
“码”力全开4 小时前
AI视频分析误报优化完整流程
算法·架构·边缘计算