题解:洛谷 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
【核心思想】
-
问题分析:给定 \(H\) 头荷斯坦牛和 \(G\) 头根西牛的二维坐标,要求从荷斯坦牛 \(1\) 出发,按编号顺序交错访问所有奶牛,最终在荷斯坦牛 \(H\) 结束。移动代价为欧几里得距离的平方。求最小总能量。这是一个线性 DP 问题,核心在于状态需记录两类奶牛的访问进度和当前所在位置类型。
-
算法选择:
- 二维线性 DP(状态扩展):\(f[i][j][0/1]\) 表示已访问 \(i\) 头荷斯坦牛和 \(j\) 头根西牛,当前位于荷斯坦牛(状态 \(0\))或根西牛(状态 \(1\))的最小能量
- 交错序列建模:两类奶牛必须各自按编号顺序访问,等价于求两个序列的最优合并路径
-
关键步骤:
- 初始化:读取 \(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)\)
- 当前在荷斯坦牛 \(i\)(\(f[i][j][0]\)):
- 输出答案:\(f[H][G][0]\)(最终必须在荷斯坦牛 \(H\))
-
时间/空间复杂度:
- 时间复杂度:\(O(H \times G)\),双层循环,每次 \(O(1)\) 转移
- 空间复杂度:\(O(H \times G)\),三维 DP 数组
-
线性 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
浙公网安备 33010602011771号