【C++】洛谷P3073 [USACO13FEB] Tractor S

P3073 [USACO13FEB] Tractor S

https://www.luogu.com.cn/problem/P3073

题目描述

FJ 有块农田太崎岖了,他要买一辆新拖拉机才能在这里巡视。这块农田由 N × N N \times N N×N 个格子的非负整数表示高度( 1 ≤ N ≤ 500 1 \le N \le 500 1≤N≤500)。拖拉机从当前格子走到相邻格子(东、南、西、北四个方向)的代价为高度差 D D D,则 FJ 驶过这两个格子的拖拉机最少也要值 D D D 块钱。

FJ 愿意花足够的钱买一辆新的拖拉机使得他能以最小的高度差走遍所有格子的一半(如果格子总数是奇数,那么一半的值为四舍五入的值)。因为 FJ 很懒,所以他找到你帮他编程计算他最小需要花多少钱买到符合这些要求的拖拉机。

输入格式

第一行为一个整数 N N N。

第 2 2 2 到 N + 1 N+1 N+1 行每行包含 N N N 个非负整数(不超过 10 6 10^6 106),表示当前格子的高度。

输出格式

共一行,表示 FJ 买拖拉机要花的最小价钱。

输入输出样例 #1

输入 #1

复制代码
5 
0 0 0 3 3 
0 0 0 0 3 
0 9 9 3 3 
9 9 9 3 3 
9 9 9 9 3 

输出 #1

复制代码
3 

题解

一、题目分析

核心问题

  • 目标:最小化拖拉机价值 ( D )。
  • 约束:连通区域内相邻格子高度差 ≤ ( D ),且区域大小 ≥ ( K )(( K ) 是总格子数的一半,向上取整)。

二、算法选择

1. 二分答案

  • 原理:( D ) 的取值具有单调性(( D ) 越大,越容易满足条件)。
  • 步骤:二分查找 ( D ) 的可能值,对每个 ( D ) 检查是否存在符合条件的连通区域。

2. 并查集

  • 原理:快速处理动态连通性问题,支持合并集合和查找根节点操作。
  • 步骤:对于每个候选 ( D ),用并查集合并高度差 ≤ ( D ) 的相邻格子,统计最大连通块大小。

三、代码实现

1. 数据结构

cpp 复制代码
struct UnionFind {
    int parent;  // 父节点
    int size;    // 集合大小(仅根节点有效)
} uf[MAX_NODE];  // 结构体数组,存储每个格子的并查集状态

2. 关键函数

find 函数(路径压缩)
cpp 复制代码
int find(int x) {
    while (uf[x].parent != x) {
        uf[x].parent = uf[uf[x].parent].parent;  // 路径压缩
        x = uf[x].parent;
    }
    return x;
}
  • 作用:找到 ( x ) 所在集合的根节点,并进行路径压缩,加速后续查找。
unite 函数(按秩合并)
cpp 复制代码
void unite(int x, int y) {
    x = find(x);  // 找到x的根节点
    y = find(y);  // 找到y的根节点
    if (x == y) return;  // 同一集合,无需合并
    if (uf[x].size < uf[y].size) {  // 按大小合并
        uf[x].parent = y;  // 小集合合并到大集合
        uf[y].size += uf[x].size;  // 更新大集合大小
    } else {
        uf[y].parent = x;
        uf[x].size += uf[y].size;
    }
}
  • 调用时机 :在 check 函数中,当相邻格子的高度差 ≤ ( mid ) 时调用。
  • 功能:合并两个相邻格子所在的集合,保持树的高度较低,提高后续操作效率。
check 函数
cpp 复制代码
bool check(int mid) {
    // 1. 初始化并查集
    for (int i = 0; i < N * N; i++) {
        uf[i].parent = i;  // 每个格子初始为独立集合
        uf[i].size = 1;     // 集合大小为1
    }

    // 2. 合并相邻且高度差符合条件的格子
    for (int i = 0; i < N; i++) {
        for (int j = 0; j < N; j++) {
            // 检查右边格子
            if (j + 1 < N) {
                int d = abs(h[i][j] - h[i][j+1]);
                if (d <= mid) {
                    int u = i * N + j;      // 当前格子编号
                    int v = i * N + (j + 1);  // 右边格子编号
                    unite(u, v);  // 合并集合
                }
            }
            // 检查下边格子(避免重复)
            if (i + 1 < N) {
                // 同理...
            }
        }
    }

    // 3. 统计最大连通块大小
    int max_size = 0;
    for (int i = 0; i < N * N; i++) {
        if (uf[i].parent == i) {  // 根节点
            if (uf[i].size > max_size) {
                max_size = uf[i].size;
            }
        }
    }
    return max_size >= K;  // 判断是否满足条件
}

的拖拉机价值 ( D ),满足题目要求。

完整代码

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

const int MAXN = 505;
const int MAX_NODE = MAXN * MAXN;

struct UnionFind {
    int parent;
    int size;
} uf[MAX_NODE];

int N, K;
int h[MAXN][MAXN];

int find(int x) {
  while (uf[x].parent != x) {
    uf[x].parent = uf[uf[x].parent].parent;
    x = uf[x].parent;
  }
  return x;
}

void unite(int x, int y) {
  x = find(x);
  y = find(y);
  if (x == y) return;
  if (uf[x].size < uf[y].size) {
    uf[x].parent = y;
    uf[y].size += uf[x].size;
  } else {
    uf[y].parent = x;
    uf[x].size += uf[y].size;
  }
}

bool check(int mid) {
  // 初始化并查集
  for (int i = 0; i < N * N; i++) {
    uf[i].parent = i;
    uf[i].size = 1;
  }

  // 合并相邻格子
  for (int i = 0; i < N; i++) {
    for (int j = 0; j < N; j++) {
      // 右边格子
      if (j + 1 < N) {
        int d = abs(h[i][j] - h[i][j+1]);
        if (d <= mid) {
          int u = i * N + j;
          int v = i * N + (j + 1);
          unite(u, v);
        }
      }
      // 下边格子
      if (i + 1 < N) {
        int d = abs(h[i][j] - h[i+1][j]);
        if (d <= mid) {
          int u = i * N + j;
          int v = (i + 1) * N + j;
          unite(u, v);
        }
      }
    }
  }

  // 统计最大连通块大小
  int max_size = 0;
  for (int i = 0; i < N * N; i++) {
    if (uf[i].parent == i) {
      if (uf[i].size > max_size) {
        max_size = uf[i].size;
      }
    }
  }
  return max_size >= K;
}

int main() {
  scanf("%d", &N);
  for (int i = 0; i < N; i++) {
    for (int j = 0; j < N; j++) {
      scanf("%d", &h[i][j]);
    }
  }

  // 计算K:总格子数的一半(向上取整)
  int total = N * N;
  K = total / 2;
  if (total % 2 == 1) {
    K += 1;
  }

  // 确定二分右边界:所有相邻格子的最大高度差
  int right = 0;
  for (int i = 0; i < N; i++) {
    for (int j = 0; j < N; j++) {
      if (j + 1 < N) {
        int d = abs(h[i][j] - h[i][j+1]);
        if (d > right) {
          right = d;
        }
      }
      if (i + 1 < N) {
        int d = abs(h[i][j] - h[i+1][j]);
        if (d > right) {
          right = d;
        }
      }
    }
  }

  // 二分查找
  int left = 0;
  int ans = right;
  while (left <= right) {
    int mid = (left + right) / 2;
    if (check(mid)) {
      ans = mid;
      right = mid - 1;
    } else {
      left = mid + 1;
    }
  }

  cout << ans << endl;
  return 0;
}
相关推荐
进击的小头2 小时前
行为型模式:策略模式的C语言实战指南
c语言·开发语言·策略模式
Aevget2 小时前
MFC扩展库BCGControlBar Pro v37.2新版亮点:控件功能进一步升级
c++·mfc·界面控件
天马37982 小时前
Canvas 倾斜矩形绘制波浪效果
开发语言·前端·javascript
Tansmjs3 小时前
C++与GPU计算(CUDA)
开发语言·c++·算法
qx093 小时前
esm模块与commonjs模块相互调用的方法
开发语言·前端·javascript
Suchadar3 小时前
if判断语句——Python
开发语言·python
莫问前路漫漫4 小时前
WinMerge v2.16.41 中文绿色版深度解析:文件对比与合并的全能工具
java·开发语言·python·jdk·ai编程
九皇叔叔4 小时前
【03】SpringBoot3 MybatisPlus BaseMapper 源码分析
java·开发语言·mybatis·mybatis plus
挖矿大亨4 小时前
c++中的函数模版
java·c++·算法
阿基米东4 小时前
基于 C++ 的机器人软件框架(具身智能)开源通信库选型分析
c++·机器人·开源