洛谷题单指南-进阶搜索-P3959 [NOIP 2017 提高组] 宝藏
原题链接:https://www.luogu.com.cn/problem/P3959
题意解读:n个节点m条边的带权无向图,从任意一个点start开始遍历,每次已访问节点集合中选取一个点u,扩展一个未访问过的点v,扩展点v的代价是从start到u的深度 * u到v的权值,直到访问完所有节点,求所有方案中代价的最小值。
解题思路:
考虑DFS搜索来解决,关键在于剪枝。
一、搜索顺序
从1~n每个点开始进行DFS
DFS时从已经访问过的点,找到所有领接点,如果未访问过,就访问,继续DFS
二、剪枝优化
1、小优化:DFS内部要遍历所有已经访问过的节点,可以通过二进制整数stat记录已经访问过节点的状态,如节点2已访问stat | 1 << (2 - 1)。
这样来枚举所有已访问过的节点,只需要找stat中所有的1,利用lowbit很容易实现,减少搜索次数。
2、中优化:最优性剪枝,DFS过程中要实时计算以访问过节点的代价cost,如果cost超过此时最新的答案ans,则退出DFS。
3、大优化:记忆化,当已经访问过一批节点时,会产生一个代价,这批节点当前的状态对应的最小代价可以记录下来,如果之后再搜索到同样一批节点的代价小于等于已经记录的相同状态的代价,则退出DFS。
那么问题是,如何表示已访问一批节点的代价?关键在三个维度:
a、所有节点编号,可以用过二进制整数表示,同上述stat
b、最后一个访问的节点
c、最后一个节点距离起点的深度
有了以上三个维度,就可以确定一批节点访问的状态
用三维数组来表示此状态即可int f[1 << 12][N][N]
三、核心流程
1、建图,采用领接矩阵即可,注意可以去除重边
2、从1~n点依次进行DFS
3、DFS过程中的几个关键参数:
u:最近访问的节点 cnt:已访问过多少节点 cost:当前总代价 stat:已访问过节点的状态,12位二进制表示
100分代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 20, INF = 0x3f3f3f3f;
int n, m, ans = INF;
int g[N][N]; //邻接矩阵
int depth[N]; //节点深度
bool vis[N]; //节点是否已访问
int lg2[1 << 12]; //初始化lg2[2^i]=i
//f[stat][u][d]表示当前已经访问过的节点状态是stat,最后访问的节点是u,u所在深度是d时的最小代价
//stat是一个12位二进制,如果i号节点访问过,则第i位是1
int f[1 << 12][N][N];
int lowbit(int x)
{
return x & -x;
}
//u:最近访问的节点 cnt:已访问过多少节点 cost:当前总代价 stat:已访问过节点的状态,12位二进制表示
void dfs(int u, int cnt, int cost, int stat)
{
if(cost >= ans) return; //最优性剪枝
if(f[stat][u][depth[u]] <= cost) return; //最优性剪枝
f[stat][u][depth[u]] = cost; //记忆化
if(cnt == n)
{
ans = min(ans, cost);
return;
}
for(int i = stat; i; i -= lowbit(i)) //枚举当前已经被访问过的节点
{
int u = lg2[lowbit(i)] + 1; //计算节点编号
for(int v = 1; v <= n; v++) //枚举u的连接边
{
if(g[u][v] == INF || vis[v]) continue;
depth[v] = depth[u] + 1; //更新深度
vis[v] = true; //标记
dfs(v, cnt + 1, cost + depth[u] * g[u][v], stat | (1 << (v - 1)));
vis[v] = false; //回溯
}
}
}
int main()
{
//初始化lg2[2^i]=i
for(int i = 1; i <= 12; i++) lg2[1 << i] = i;
cin >> n >> m;
int u, v, w;
memset(g, 0x3f, sizeof(g));
while(m--)
{
cin >> u >> v >> w;
if(u == v) continue; //跳过重边
g[u][v] = g[v][u] = min(g[u][v], w); //建立双向边
}
memset(f, 0x3f, sizeof(f)); //放到全局更快
for(int i = 1; i <= n; i++)
{
memset(vis, 0, sizeof(vis));
memset(depth, 0, sizeof(depth));
vis[i] = true;
depth[i] = 1;
dfs(i, 1, 0, 1 << (i - 1));
}
cout << ans;
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?