把每一步在每个小组的最小代价做成阶段型 DP,状态只可能来自本组或前一组。
OJ: luogu
题目 ID: P1130
难度:普及-
标签:dp动态规划
日期: 2026-06-19 11:01
题意
有 m 个小组,要完成 n 个步骤。
第 i 组做第 j 步需要 cost[i][j] 天。
每一步你可以:
- 继续留在当前组;
- 或在相邻两步之间切换到下一组(
m的下一组视作1)。
要求求出完成全部步骤的最小总代价。
思路
最直接的教学版做法是搜索每一步“留在本组”还是“切到下一组”:
cpp
// brute.cpp:搜索所有“留在本组/切到下一组”的方案,作为教学版和对拍基准程序。
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 25;
const long long INF = (1LL << 60);
int n, m;
int cost_days[MAXN][MAXN];
long long ans = INF;
int next_group(int x) {
if (x == m) {
return 1;
}
return x + 1;
}
void dfs(int step, int group, long long sum) {
if (sum >= ans) {
return;
}
if (step == n) {
ans = min(ans, sum);
return;
}
dfs(step + 1, group, sum + cost_days[group][step + 1]);
int ng = next_group(group);
dfs(step + 1, ng, sum + cost_days[ng][step + 1]);
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> m;
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
cin >> cost_days[i][j];
}
}
for (int start = 1; start <= m; start++) {
dfs(1, start, cost_days[start][1]);
}
cout << ans << '\n';
return 0;
}但正式解法用 DP 更自然。
设 dp[i] 表示当前处理到某一步时,停在第 i 组的最小总代价。
如果这一列要到第 i 组,那么上一列只能来自:
- 原本就在第
i组; - 原本在第
i的前一个组,然后本步切换过来。
所以转移是:
new_dp[i] = min(dp[i], dp[prev(i)]) + cost[i][step]
第一步可以任选小组,因此直接初始化为第一列代价。
DP 公式
设
第
其中:
最终答案为:
公式解释:第 step 步停在第 i 组时,上一刻只能已经在第 i 组,或者从环上的前一组转过来。两者取较小值后再加上当前组完成这一步的代价,就得到新的最小总代价。
代码
cpp
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 2005;
const long long INF = (1LL << 60);
int n, m;
int cost_days[MAXN][MAXN]; // cost_days[i][j]:第 i 组做第 j 步所需天数
long long dp[MAXN]; // 当前处理到某一步时,dp[i] 表示在第 i 组的最小代价
long long ndp[MAXN];
int prev_group(int x) {
if (x == 1) {
return m;
}
return x - 1;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> m;
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
cin >> cost_days[i][j];
}
}
for (int i = 1; i <= m; i++) {
dp[i] = cost_days[i][1];
}
for (int step = 2; step <= n; step++) {
for (int group = 1; group <= m; group++) {
ndp[group] = min(dp[group], dp[prev_group(group)]) + cost_days[group][step];
}
for (int group = 1; group <= m; group++) {
dp[group] = ndp[group];
}
}
long long ans = INF;
for (int i = 1; i <= m; i++) {
ans = min(ans, dp[i]);
}
cout << ans << '\n';
return 0;
}复杂度
- 时间复杂度:
- 空间复杂度:
总结
这题的本质是一个很标准的“按列推进”的阶段型 DP。
关键只在于看清楚:每个状态的来源永远只有两个。
一图流解析
这张图把本题的建模、关键转移、实现检查和训练方法压缩到一页,适合读完正文后复盘。
