P3959 [NOIP2017 提高组] 宝藏 题解
一道不错的状压 dp 题。
注意到本质上打通的路径会构成一棵树,因此实际上总花费就是一个点的层高(根节点层高为 0)乘上其到父亲节点的边的边权。
据此可以考虑一种初步的状压 dp 方式(下文点编号 ),设 表示层高为 ,当前已经选的点集为 时的最小花费,转移时考虑枚举 层点集 , 层点集 ,则转移方程 ,其中 表示 挖去 后剩下的集合, 表示 中的每个点 到 中任意一个点的最短边长之和(对每个 选出来的边的另一端端点可以不同),复杂度 ,过不去。
然而因为这题要求是求最小花费,因此可以考虑优化一下转移过程:转移时枚举前 层点集 ,自然 层点集 ,则转移方程 (我代码实现里面写的是枚举 ,差别不大)。
这样子看起来似乎多了很多不合法方案,比如说 层可以直接连到 层了,层数似乎不对?
但是注意到如果出现这种情况,那么最后答案一定是被放大了的,比如 层的点直接连到 层,本来层数是 但是我们强制算成了 ,放大了答案,而原先的正确连接方式 层连到 层并没有从方案中剔除,所以这个 dp 还是正确的。
这样只需要枚举 和 及其子集,复杂度 (用 枚举子集的方法)
现在问题变成了如何在复杂度内计算 ,更一般的就是 ,此时我们可以考虑从 里面拿个点 出来,则 ,其中 表示点 到集合 内所有点连边的边权最小值。我们希望 最小,因此看起来还要枚举 以保证最小,但是注意到转移式子中 是不动的,我们只是从 中拿出点 ,因此无论拿哪个点都是一样的(每个点选择的边都是一样的),因此直接转移即可,选 时为了方便直接选 即可,这块复杂度 ,只需要处理 。
至于 ,可以直接 处理,当然也可以从 选个点出来,因为 到剩余点选择的边是定的,只是往里面多加入一条边取最小值而已,至于两点之间距离直接邻接矩阵存下就完事了。
然后有几个细节需要注意:
- 初值 ,因为 到 0 无法连出任何边,剩下的全赋 INF 也行不赋也行(因为剩下的转移用不到自身)。
- 初值 其余为 INF,原因是空集向集合 连边最小值显然为 0,这里与 向空集连边不同的点在于, 向空集连边但是空集里面没有点,所以连边会失败,默认 INF,但是空集向 连边时因为没有点,所以其实连边已经成功了,边长度为 0。
- 初值 其余为 INF。
- 邻接矩阵初始全 INF。
- 计算 的时候注意一下不要让 超出 INF 范围。
- 建议 INF 为 0x3f3f3f3f(int)/0x3f3f3f3f3f3f3f3f(long long),这样可以保证 转移时不会炸范围。
- 转移时看一眼当前的 是否为 INF,不是再转移。实际上 能为 INF 也只有第二维为 0 或者 中有个点没有与 内所有点的直接连边。
- 在实际转移 时,不需要考虑 无交条件,首先所有合法的 一定都是从合法状态转移而来(挖去一个点两者不可能有交),其次 转移时由于是枚举子集然后挖掉,因此两者同样无交,所以非法状态就随意了。
Code:
/*
========= Plozia =========
Author:Plozia
Problem:P3959 [NOIP2017 提高组] 宝藏
Date:2022/10/16
========= Plozia =========
*/
#include <bits/stdc++.h>
typedef long long LL;
const int MAXN = 12 + 5, MAXP = (1 << 12) + 5;
const LL INF = 0x3f3f3f3f3f3f3f3f;
int n, m, dis[MAXN][MAXN], Bin[MAXP];
LL f[MAXN][MAXP], r[MAXP][MAXP], b[MAXN][MAXP];
int Read()
{
int sum = 0, fh = 1; char ch = getchar();
for (; ch < '0' || ch > '9'; ch = getchar()) fh -= (ch == '-') << 1;
for (; ch >= '0' && ch <= '9'; ch = getchar()) sum = (sum << 3) + (sum << 1) + (ch ^ 48);
return sum * fh;
}
LL Min(LL x, LL y) { return (x < y) ? x : y; }
int main()
{
n = Read(), m = Read(); memset(dis, 0x3f, sizeof(dis));
if (n == 1) { puts("0"); return 0; }
for (int i = 0; i <= 12; ++i) Bin[1 << i] = i + 1;
for (int i = 1; i <= m; ++i)
{
int x = Read(), y = Read(), z = Read();
dis[x][y] = dis[y][x] = Min(dis[x][y], z);
}
for (int i = 1; i <= n; ++i)
{
b[i][0] = INF;
for (int j = 1; j < (1 << n); ++j)
{
int k = j & (-j);
b[i][j] = Min(b[i][j ^ k], dis[i][Bin[k]]);
}
}
memset(r, 0x3f, sizeof(r));
for (int i = 0; i < (1 << n); ++i) r[0][i] = 0;
for (int i = 1; i < (1 << n); ++i)
{
for (int j = 0; j < (1 << n); ++j)
{
int k = i & (-i);
r[i][j] = Min(INF, r[i ^ k][j] + b[Bin[k]][j]);
}
}
memset(f, 0x3f, sizeof(f));
for (int i = 1; i <= n; ++i) f[0][1 << (i - 1)] = 0;
for (int h = 1; h < n; ++h)
{
for (int i = 1; i < (1 << n); ++i)
{
for (int j = i; j; j = (j - 1) & i)
{
if (r[j][i ^ j] != INF) f[h][i] = Min(f[h][i], f[h - 1][i ^ j] + r[j][i ^ j] * h);
}
}
}
LL ans = 0x7f7f7f7f7f7f7f7f;
for (int h = 1; h < n; ++h) ans = Min(ans, f[h][(1 << n) - 1]);
printf("%lld\n", ans); return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探