当前位置: 首页 > news >正文

洛谷 P1171:售货员的难题 ← 状态压缩DP + 最短 Hamilton 回路

【题目来源】
https://www.luogu.com.cn/problem/P1171

【题目描述】
某乡有 n 个村庄,有一个售货员,他要到各个村庄去售货,各村庄之间的路程 sᵢⱼ​ 是已知的,且 A 村到 B 村与 B 村到 A 村的路大多不同。为了提高效率,他从商店出发到每个村庄一次,然后返回商店所在的村,假设商店所在的村庄为 1,他不知道选择什么样的路线才能使所走的路程最短。请你帮他选择一条最短的路。

【输入格式】
第一行是一个整数,表示村庄数 n。
接下来 n 行,每行 n 个整数,第 i 行的第 j 个整数表示 i 到 j 的单向路径的距离 sᵢⱼ​。

【输出格式】
一行一个整数表示最短的路程。

【输入样例】
3
0 2 1
1 0 2
2 1 0

【输出样例】
3

【数据范围】
对全部的测试数据,保证 2≤n≤20,1≤sᵢⱼ​<10^3。

【算法分析】
● 本题的本质是求有向图的最小权值哈密顿回路(Hamilton 回路)。
算法要求从村庄 1 出发,不重复走遍所有村庄,最后回到村庄 1,求最小的总路程。但是,题目给出的 n=20 时,全排列有 20! ≈ 2.433×10¹⁸ 种路线,就算剪枝,也不可能跑完,必然超时(TLE)。下面给出的是 dfs 写法的 80 分代码。

#include <bits/stdc++.h>
using namespace std;const int inf=0x3f3f3f3f;
const int N=25;
int g[N][N];
bool st[N];
int ans=inf;
int n;// pos: current village (node)
// cnt: number of villages visited so far
// dis: total distance traveled so far
void dfs(int pos,int cnt,int dis) {if(dis>=ans) return;if(cnt==n) {ans=min(ans,dis+g[pos][1]);return;}for(int i=1; i<=n; i++) {if(!st[i]) {st[i]=true;dfs(i,cnt+1,dis+g[pos][i]);st[i]=false;}}
}int main() {memset(st,0,sizeof st);cin>>n;for(int i=1; i<=n; i++) {for(int j=1; j<=n; j++) {cin>>g[i][j];}}st[1]=true;dfs(1,1,0);cout<<ans<<endl;return 0;
}/*
in:
3
0 2 1
1 0 2
2 1 0out:
3
*/

● 本题(洛谷 P1171:售货员的难题)的标准正解,只有用状态压缩 DP 才能 AC。
(洛谷 P1171:售货员的难题)是“状态压缩DP + 最短 Hamilton 回路”的模板题。
(洛谷 P10447:最短 Hamilton 路径)是“状态压缩DP + 最短 Hamilton 路径”的模板题。
仅需将(洛谷 P10447:最短 Hamilton 路径)中的代码 cout<<dp[(1<<n)-1][n-1]<<endl; 修改为如下代码,即可得到(洛谷 P1171:售货员的难题)的代码。

int ans=inf;
for(int i=0; i<n; i++) {ans=min(ans,dp[(1<<n)-1][i]+g[i][0]);
}
cout<<ans<<endl;

● 本题核心代码解析
(1)const int N=20; → 最多 20 个点,20 是状压 DP 的极限(2²⁰ ≈ 100 万)。
(2)int dp[1<<N][N]; → 最重要的核心!
例如,dp[state][u] 中的 state(二进制) 表示哪些点已经走过,u 表示当前停在哪个点。dp[state][u] 的值表示走完这些点的最短路径长度。比如,dp[1011][3] 表示走过点 0、1、3,当前停在点 3 的最短路径的值。dp[1<<0][0]=0 表示走过点 0,当前停在点 0 的最短路径的值为 0(起点)。
(3)三层循环(状态转移)→ 在所有可能的局面里,尝试从每一个当前点,走向每一个能走的下一点,并记录最短路径

for(int i=0; i<(1<<n); i++) {       // 枚举所有状态for(int j=0; j<n; j++) {        // 枚举当前点 jif(!(i&(1<<j))) continue;   // j 没走过,跳过for(int k=0; k<n; k++) {    // 枚举下一个点 kif(i&(1<<k)) continue;  // k 走过了,跳过int t=i|(1<<k);         // 新状态:把 k 加进去dp[t][k]=min( dp[t][k],dp[i][j]+g[j][k] );}}
}

① for(int i=0; i<(1<<n); i++) → 枚举所有可能的状态。每个 i 是一个二进制数,表示哪些点走过了。
② for(int j=0; j<n; j++) → 枚举当前停在哪个点 j。
③ if( !(i & (1<<j)) ) continue; → 如果状态 i 里没有 j,说明 j 还没走过,跳过。
④ for(int k=0; k<n; k++) → 尝试从 j 走到 k,k 是下一个要走的点。
⑤ if( i & (1<<k) ) continue; → k 已经走过,不能重复走, 跳过。
⑥ int t = i | (1<<k); → 把 k 加入已走集合,得到新状态 t。
⑦ dp[t][k] = min( ... ) → 从 j 走到 k,新路径 = 旧路径 + j 到 k 的距离,保留最小值。
(4)cout << dp[ (1<<n) -1 ][n-1] << endl; → (1<<n)-1 等于二进制全 1,表示所有点都走过了。n-1 表示最后停在终点。

【算法代码:状态压缩DP + 最短 Hamilton 回路

#include <bits/stdc++.h>
using namespace std;const int inf=0x3f3f3f3f;
const int N=20;
int dp[1<<N][N];
int g[N][N];
int n;int main() {cin>>n;for(int i=0; i<n; i++) {for(int j=0; j<n; j++) {cin>>g[i][j];}}memset(dp,inf,sizeof dp);dp[1<<0][0]=0;for(int i=0; i<(1<<n); i++) {for(int j=0; j<n; j++) {if(!(i&(1<<j))) continue;for(int k=0; k<n; k++) {if(i&(1<<k)) continue;int t=i|(1<<k);dp[t][k]=min(dp[t][k],dp[i][j]+g[j][k]);}}}//cout<<dp[(1<<n)-1][n-1]<<endl;int ans=inf;for(int i=0; i<n; i++) {ans=min(ans,dp[(1<<n)-1][i]+g[i][0]);}cout<<ans<<endl;return 0;
}/*
in:
3
0 2 1
1 0 2
2 1 0out:
3
*/



【参考文献】
https://blog.csdn.net/hnjzsyjyj/article/details/161041041
https://blog.csdn.net/hnjzsyjyj/article/details/161025792
https://www.luogu.com.cn/problem/P1713
https://www.cnblogs.com/yinyuqin/p/14028156.html
https://www.cnblogs.com/tuchen/p/13829388.html
https://www.cnblogs.com/LJB666/p/11223989.html
https://www.cnblogs.com/linxiaoxu/p/17679355.html


 

http://www.jsqmd.com/news/811006/

相关文章:

  • xxl-job 任务执行中却被判定丢失?从一次“幽灵任务”排查看调度队列与健康检查的陷阱
  • 避坑指南:TI CC2530在IAR for 8051中新建工程最常见的5个配置错误及解决方法
  • 3步快速上手:Windows电脑直接安装安卓应用的终极指南
  • Dirty Frag漏洞深度解析:Copy Fail终极继任者,无补丁PoC公开引爆Linux安全危机
  • 如何用30秒免费获取百度文库完整文档?这个开源脚本给你答案
  • 基于Rust事件驱动引擎barter-rs的量化交易策略开发实践
  • 天津复读择校指南:不同分数段学生怎么选?5 所院校适配性解析 - 外贸老黄
  • 2026年企业级SCA工具选型对比:Gitee CodePecker SCA与开源方案的深度解析
  • 强力突破:3分钟掌握MediaCreationTool.bat全能Windows安装方案
  • Canvas LMS 2.75亿用户数据泄露全复盘:ShinyHunters攻击链拆解与教育SaaS安全重构
  • 半导体行业整合如何影响研发投入与创新生态?
  • 镜像视界多相机融合算法|跨镜轨迹全域跟踪,无感定位智慧场景解决方案
  • 绵阳哪个茶楼最好 - GrowthUME
  • 基于AI的Obsidian智能闪卡生成器:提升学习记忆效率的利器
  • 2026年中国AI生态核心实践推荐:模力方舟与口袋龙虾如何定义自主可控
  • 电磁兼容(EMC)设计实战:从干扰源头到系统防护的完整指南
  • 告别调试助手:在Linux终端用minicom高效收发AT指令
  • AI 少儿英语阅读 APP的功能
  • Agent工作流卡顿、循环、幻觉频发?Lindy官方未公开的3层诊断协议首次披露
  • Origin实战:从数据拟合到曲线切线的精准绘制
  • 2026年DevOps平台选型:Gitee的核心优势与实用推荐
  • 2026年GEO行业趋势:从流量分发到信任锚定,企业该如何破局? - 麒麟芯geo4008005528
  • 智能图像去重革命:用AntiDupl.NET拯救你的数字存储空间
  • YOLOv8 cad图纸识别 建筑物风格识别 筑蓝图风格检测 图像中门窗自动检测
  • 无感定位技术解析
  • AI文本检测技术解析:从原理到实践,如何有效识别AI生成内容
  • Nodeunit源码探秘:核心模块与异步测试实现原理
  • Project Eye视力保护工具终极指南:20-20-20规则智能提醒守护你的数字健康
  • 2026年国内团队代码托管平台选型推荐:Gitee如何成为效率与合规之选
  • 从0到10万MAU的Lovable跃迁路径:3个被低估的非技术杠杆,第2个连CTO都常忽略