题解:洛谷 P2848 Cow Checklist

【题目来源】

洛谷:P2848 [USACO16DEC] Cow Checklist G

【题目描述】

每天,Farmer John 都会穿过他的牧场,检查每头奶牛的健康状况。他的农场里有两类奶牛:荷斯坦牛和根西牛。他的 \(H\) 头荷斯坦牛被方便地编号为 \(1 \ldots H\),而他的 \(G\) 头根西牛被方便地编号为 \(1 \ldots G\)\(1 \leq H \leq 1000, 1 \leq G \leq 1000\))。每头奶牛都位于二维平面中的一个点(不一定不同)。

Farmer John 从荷斯坦牛 \(1\) 开始他的巡视,并在荷斯坦牛 \(H\) 结束。他希望沿途访问每头奶牛,并且为了方便维护他已经访问过的奶牛清单,他希望按照编号顺序访问荷斯坦牛和根西牛。在他访问的所有 \(H+G\) 头奶牛的序列中,编号为 \(1 \ldots H\) 的荷斯坦牛应作为一个(不一定连续的)子序列出现,同样地,编号为 \(1 \ldots G\) 的根西牛也应如此。换句话说,所有 \(H+G\) 头奶牛的序列应通过将编号为 \(1 \ldots H\) 的荷斯坦牛列表与编号为 \(1 \ldots G\) 的根西牛列表交错排列而成。

当 Farmer John 从一头奶牛移动到另一头奶牛,移动距离为 \(D\) 时,他会消耗 \(D^2\) 的能量。请帮助他确定按照上述巡视方式访问所有奶牛所需的最小能量。

【输入】

输入的第一行包含用空格分隔的 \(H\)\(G\)

接下来的 \(H\) 行包含 \(H\) 头荷斯坦牛的 \(x\)\(y\) 坐标,随后的 \(G\) 行包含根西牛的坐标。每个坐标都是 \(0 \ldots 1000\) 范围内的整数。

【输出】

输出一行,表示 Farmer John 巡视所有奶牛所需的最小能量。

【输入样例】

3 2
0 0
1 0
2 0
0 3
1 3

【输出样例】

20

【核心思想】

  1. 问题分析:给定 \(H\) 头荷斯坦牛和 \(G\) 头根西牛的二维坐标,要求从荷斯坦牛 \(1\) 出发,按编号顺序交错访问所有奶牛,最终在荷斯坦牛 \(H\) 结束。移动代价为欧几里得距离的平方。求最小总能量。这是一个线性 DP 问题,核心在于状态需记录两类奶牛的访问进度和当前所在位置类型。

  2. 算法选择

    • 二维线性 DP(状态扩展)\(f[i][j][0/1]\) 表示已访问 \(i\) 头荷斯坦牛和 \(j\) 头根西牛,当前位于荷斯坦牛(状态 \(0\))或根西牛(状态 \(1\))的最小能量
    • 交错序列建模:两类奶牛必须各自按编号顺序访问,等价于求两个序列的最优合并路径
  3. 关键步骤

    • 初始化:读取 \(H\)\(G\)、坐标数组 \(H[1..H]\)\(G[1..G]\)
    • 边界\(f[1][0][0] = 0\)(从荷斯坦牛 \(1\) 出发,已访问 \(1\) 头荷斯坦牛、\(0\) 头根西牛)
    • DP 递推\(i\)\(1\)\(H\)\(j\)\(0\)\(G\)):
      • 当前在荷斯坦牛 \(i\)\(f[i][j][0]\)):
        • 从根西牛 \(j\) 转移(\(j \geq 1\)):\(f[i][j][0] = \min(f[i][j][0], f[i-1][j][1] + dist(H_i, G_j)^2)\)
        • 从荷斯坦牛 \(i-1\) 转移(\(i \geq 2\)):\(f[i][j][0] = \min(f[i][j][0], f[i-1][j][0] + dist(H_i, H_{i-1})^2)\)
      • 当前在根西牛 \(j\)\(f[i][j][1]\)\(j \geq 1\)):
        • 从荷斯坦牛 \(i\) 转移:\(f[i][j][1] = \min(f[i][j][1], f[i][j-1][0] + dist(G_j, H_i)^2)\)
        • 从根西牛 \(j-1\) 转移(\(j \geq 2\)):\(f[i][j][1] = \min(f[i][j][1], f[i][j-1][1] + dist(G_j, G_{j-1})^2)\)
    • 输出答案\(f[H][G][0]\)(最终必须在荷斯坦牛 \(H\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(H \times G)\),双层循环,每次 \(O(1)\) 转移
    • 空间复杂度:\(O(H \times G)\),三维 DP 数组
  5. 线性 DP 的核心思想

    • 双序列交错模型:将两个有序序列的合并问题转化为网格图上的最短路径,\(i\)\(j\) 分别表示两个序列的访问进度
    • 状态设计的关键:必须记录"当前所在位置类型"(荷斯坦还是根西),因为下一步的移动代价取决于当前位置
    • 四种转移全覆盖:从当前位置类型出发,上一步可能来自同类型前一头或另一类型当前头,全部枚举取最小
    • 终点约束:题目要求最终停在荷斯坦牛 \(H\),因此答案取 \(f[H][G][0]\) 而非 \(\min(f[H][G][0], f[H][G][1])\)
    • 适用于双序列合并、交错路径、多类型有序访问类 DP 问题

【算法标签】

普及+ #线性DP-一维

【代码详解】

#include <bits/stdc++.h>
using namespace std;
#define int long long // 将int定义为long long,防止坐标平方溢出
const int N = 1005; // 最大奶牛数量
int n, m; // n:荷斯坦牛数量H, m:根西牛数量G
int f[N][N][2]; // f[i][j][0/1]:已访问i头荷斯坦牛和j头根西牛,当前位于荷斯坦牛(0)或根西牛(1)的最小能量
struct Node
{
    int x, y; // 坐标
}H[N], G[N]; // H[i]:第i头荷斯坦牛坐标, G[i]:第i头根西牛坐标

signed main()
{
    cin >> n >> m; // 读入荷斯坦牛和根西牛数量
    for (int i=1; i<=n; i++) // 读入荷斯坦牛坐标
        cin >> H[i].x >> H[i].y;

    for (int i=1; i<=m; i++) // 读入根西牛坐标
        cin >> G[i].x >> G[i].y;

    // 初始化DP数组为无穷大
    memset(f, 0x3f, sizeof(f));
    // 边界条件:从荷斯坦牛1开始,已访问1头荷斯坦牛和0头根西牛,当前在荷斯坦牛
    f[1][0][0] = 0;

    for (int i=1; i<=n; i++) // 外层循环:枚举已访问的荷斯坦牛数量
        for (int j=0; j<=m; j++) // 内层循环:枚举已访问的根西牛数量
        {
            if (i==1 && j==0) continue; // 跳过初始状态

            // 状态转移:当前位于第i头荷斯坦牛
            if (i>=1)
            {
                // 情况1:从根西牛j转移过来(上一次访问的是根西牛j)
                // 注意:当j=0时,G[0]未定义,但代码中j>=1时才会用到G[j]
                // 实际上当j=0时,只能从荷斯坦牛i-1转移
                if (j>=1)
                {
                    int dx = H[i].x - G[j].x;
                    int dy = H[i].y - G[j].y;
                    int dist = dx * dx + dy * dy; // 欧几里得距离的平方
                    f[i][j][0] = min(f[i][j][0], f[i-1][j][1] + dist);
                }
                // 情况2:从荷斯坦牛i-1转移过来(上一次访问的是荷斯坦牛i-1)
                if (i>=2)
                {
                    int dx = H[i].x - H[i-1].x;
                    int dy = H[i].y - H[i-1].y;
                    int dist = dx * dx + dy * dy;
                    f[i][j][0] = min(f[i][j][0], f[i-1][j][0] + dist);
                }
            }

            // 状态转移:当前位于第j头根西牛
            if (j>=1)
            {
                // 情况1:从荷斯坦牛i转移过来(上一次访问的是荷斯坦牛i)
                int dx = G[j].x - H[i].x;
                int dy = G[j].y - H[i].y;
                int dist = dx * dx + dy * dy;
                f[i][j][1] = min(f[i][j][1], f[i][j-1][0] + dist);
                // 情况2:从根西牛j-1转移过来(上一次访问的是根西牛j-1)
                if (j>=2)
                {
                    dx = G[j].x - G[j-1].x;
                    dy = G[j].y - G[j-1].y;
                    dist = dx * dx + dy * dy;
                    f[i][j][1] = min(f[i][j][1], f[i][j-1][1] + dist);
                }
            }
        }
    // 答案:访问完所有奶牛,最终停在荷斯坦牛H(编号n)的最小能量
    cout << f[n][m][0] << endl;
    return 0;
}

【运行结果】

3 2
0 0
1 0
2 0
0 3
1 3
20
posted @ 2026-08-11 17:37  团爸讲算法  阅读(13)  评论(0)    收藏  举报