P3959 [NOIP2017 提高组] 宝藏 题解

一道不错的状压 dp 题。

注意到本质上打通的路径会构成一棵树,因此实际上总花费就是一个点的层高(根节点层高为 0)乘上其到父亲节点的边的边权。

据此可以考虑一种初步的状压 dp 方式(下文点编号 1n),设 fh,S 表示层高为 h,当前已经选的点集为 S 时的最小花费,转移时考虑枚举 h1 层点集 Sh 层点集 S,则转移方程 fh,S=min{fh1,S/S+rS,S×h},其中 S/S 表示 S 挖去 S 后剩下的集合,rS,S 表示 S 中的每个点 xS 中任意一个点的最短边长之和(对每个 x 选出来的边的另一端端点可以不同),复杂度 O(n23n),过不去。

然而因为这题要求是求最小花费,因此可以考虑优化一下转移过程:转移时枚举 h1 层点集 S,自然 h 层点集 S/S,则转移方程 fh,S=min{fh1,S+rS/S,S×h}(我代码实现里面写的是枚举 S/S,差别不大)。

这样子看起来似乎多了很多不合法方案,比如说 h 层可以直接连到 h2 层了,层数似乎不对?

但是注意到如果出现这种情况,那么最后答案一定是被放大了的,比如 h 层的点直接连到 h2 层,本来层数是 h1 但是我们强制算成了 h,放大了答案,而原先的正确连接方式 h1 层连到 h2 层并没有从方案中剔除,所以这个 dp 还是正确的。

这样只需要枚举 hS 及其子集,复杂度 O(n3n)(用 O(3n) 枚举子集的方法)

现在问题变成了如何在复杂度内计算 rS/S,S,更一般的就是 rS1,S2,S1S2=,此时我们可以考虑从 S1 里面拿个点 x 出来,则 rS1,S2=rS1/x,S2+bx,S2,其中 bx,S 表示点 x 到集合 S 内所有点连边的边权最小值。我们希望 rS1,S2 最小,因此看起来还要枚举 x 以保证最小,但是注意到转移式子中 S2 是不动的,我们只是从 S1 中拿出点 x,因此无论拿哪个点都是一样的(每个点选择的边都是一样的),因此直接转移即可,选 x 时为了方便直接选 lowbit(S1) 即可,这块复杂度 O(4n),只需要处理 bx,S

至于 bx,S,可以直接 O(n22n) 处理,当然也可以从 S 选个点出来,因为 x 到剩余点选择的边是定的,只是往里面多加入一条边取最小值而已,至于两点之间距离直接邻接矩阵存下就完事了。

然后有几个细节需要注意:

  1. 初值 bx,0=INF,因为 x 到 0 无法连出任何边,剩下的全赋 INF 也行不赋也行(因为剩下的转移用不到自身)。
  2. 初值 r0,S=0 其余为 INF,原因是空集向集合 S 连边最小值显然为 0,这里与 S 向空集连边不同的点在于,S 向空集连边但是空集里面没有点,所以连边会失败,默认 INF,但是空集向 S 连边时因为没有点,所以其实连边已经成功了,边长度为 0。
  3. 初值 f0,1<<(x1)=0 其余为 INF。
  4. 邻接矩阵初始全 INF。
  5. 计算 r 的时候注意一下不要让 r 超出 INF 范围。
  6. 建议 INF 为 0x3f3f3f3f(int)/0x3f3f3f3f3f3f3f3f(long long),这样可以保证 r 转移时不会炸范围。
  7. f 转移时看一眼当前的 r 是否为 INF,不是再转移。实际上 rS1,S2 能为 INF 也只有第二维为 0 或者 S1 中有个点没有与 S2 内所有点的直接连边。
  8. 在实际转移 r 时,不需要考虑 S1,S2 无交条件,首先所有合法的 S1,S2 一定都是从合法状态转移而来(挖去一个点两者不可能有交),其次 f 转移时由于是枚举子集然后挖掉,因此两者同样无交,所以非法状态就随意了。

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;
}
posted @   Plozia  阅读(64)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
点击右上角即可分享
微信分享提示