首页 > 其他分享 >P3959 [NOIP2017 提高组] 宝藏 题解

P3959 [NOIP2017 提高组] 宝藏 题解

时间:2022-10-16 21:12:27浏览次数:60  
标签:NOIP2017 连边 题解 空集 枚举 P3959 层点集 INF 转移

一道不错的状压 dp 题。

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

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

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

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

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

这样只需要枚举 \(h\) 和 \(S\) 及其子集,复杂度 \(O(n3^n)\)(用 \(O(3^n)\) 枚举子集的方法)

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

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

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

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

标签:NOIP2017,连边,题解,空集,枚举,P3959,层点集,INF,转移
From: https://www.cnblogs.com/Plozia/p/16156694.html

相关文章

  • java问题解答
    因为子类继承自父类,会沿用父类的东西(没被覆盖的函数以及可见的成员变量等),而这些东西子类是没有的,需要先初始化父类才能被使用子类构造方法中调用父类构造方法,一个作用是......
  • 2021 CCPC 威海站 VP记录(题解)
    2021CCPC威海站VP记录(题解)题目顺序为vp时开题顺序:A-Goodbye,Ziyin!签到,连边数小于等于2的可以作为二叉树根,若有大于4的直接输出0。code:voidsolve(){ int......
  • [题解] Codeforces Global Round 23 1746 A B C D E1 F 题解
    点我看题求点赞A.Maxmina首先序列全0的情况肯定是NO。否则,如果\(k\ge3\),则在序列中随便找一个1,把他左边和右边分别用第一种操作不断缩,直到序列长度为k为止,最后用一次2......
  • CF1153F 题解
    传送门题意有一段长为\(l\)的线段,有\(n\)个区间,左右端点在\([0,l)\)间均匀随机(可能不是整数)。求期望被至少\(k\)段区间覆盖的长度,对\(998244353\)取膜模。题......
  • Codeforces Global Round 23题解
    T1link大水题,不想说最后一定可以把一个序列消成长度为\(k\)的带一序列,前提是其原来就有一所以贪心就是如果有一,就行,反之不行codeT2linkwssb,考试的时候居然想了大......
  • Codeforces Global Round 23 (A-E1)个人题解
    A-Maxmina给定一个01串,我们可以将k个数变为他们的最大值(k个数变成1个数),或者将相邻的两个数变为他们的最小值(2个数变成1个数),询问是否可以将这个01串变成仅含有一个1的......
  • Codeforces Global Round 23 题解
    ContestLink我是智障。A.MaxminaProblemLink显然当数组中全是\(0\)的时候,最后不可能变成\(1\),因为我们只有相邻取\(\min\)和区间取\(\max\)两种操作,并没有任......
  • AtCoder Beginner Contest 273 题解
    第一次AtCoder体验,不是很好。ARecursiveFunction原题链接大水题。只要会递归就会做。点击查看代码#include<cstdio>#include<iostream>#definelllonglong......
  • 【题解】古代猪文
    \(\textrm{luoguP2480[SDOI2010]古代猪文}\)所以说嘛,我现在才刚入数论的门。简要题意:求\(\largeg^{\sum_{d\midn}\binom{n}{d}}\)在模\(999911659\)意义下的......
  • MAC MYSQL问题解决方案
    目录下载安装添加环境变量下载安装添加环境变量zsh:commandnotfound:mysql说明环境变量没有添加上方案一:cd~vim~/.bashrc//打开的文档中加入下面这句话alia......