【NOIP2017】宝藏

传送门

这道题真的给我留下了创伤……废了4天七八个小时才AC……

先发一张耀眼的AC图:

在这里插入图片描述
啊心情好多了。

另外听说某巨佬爆搜跑了0ms,n在20以内都秒过……先%为敬,tql。

好了我们先看看数据范围 n ≤ 12 n\leq 12 n12,状压DP无疑了……

看了数据范围再看题面,这道题类似最小生成树。kruskal肯定不行,prim也会被卡掉。

最后连出的边一定形成一棵树,在计算过程中我们要在状态中保存当前树的高度计算题目中的 L L L 值。

所以状态:设 f [ i ] [ S ] f[i][S] f[i][S] 表示树高为 i + 1 i+1 i+1(这是个人的习惯,也可以设树高为 i i i ),当前联通的点为集合 S S S 中的最小代价。

方程为: f [ i ] [ S ] = f [ i − 1 ] [ S 0 ] + p a y × i ( S 0 ∈ S ) f[i][S]=f[i-1][S0]+pay\times i(S0\in S) f[i][S]=f[i1][S0]+pay×i(S0S),其中 p a y pay pay 为这次连边的总长度。

接下来的重点就落在了 p a y pay pay 的计算上。我们可以预处理一个数组, g a p [ i ] [ j ] gap[i][j] gap[i][j] 表示集合 j j j 的所有元素像集合 i i i 中的任意元素连一条边的最小花费。

再预处理一个数组, d i s [ i ] [ S ] dis[i][S] dis[i][S] 表示点 i i i 向集合 S S S 中任意一个点连一条边的最小花费。

d i s dis dis 数组灰常容易计算,有了 d i s , g a p dis,gap dis,gap 也就是唾手可得的东西了。有了 g a p gap gap f f f 数组的计算就很爽歪歪了:只需将方程修改为 f [ i ] [ S ] = f [ i − 1 ] [ S 0 ] + g a p [ S 0 ] [ S f[i][S]=f[i-1][S0]+gap[S0][S f[i][S]=f[i1][S0]+gap[S0][S ^ S 0 ] × i ( S 0 ∈ S ) \hat{} S0]\times i(S0\in S) ^S0]×i(S0S) 即可。

C o d e : Code: Code:

#include <cstdio>
#include <cstring>
#define min(x, y) (x < y ? x : y)

int mp[13][13], dis[13][1 << 12], gap[1 << 12][1 << 12], f[13][1 << 12], len[1 << 12], n;

inline bool scan() {
	memset(mp, 0x3f, sizeof(mp));
	int m, u, v, w;
	scanf("%d%d", &n, &m);
	if (m == 0) {
		putchar('0');
		return false; 
	}
	for (register int i(1); i < 1 << n; ++ i) {
		register int k(i);
		while (k) {if (k & 1) ++ len[i]; k >>= 1;}
	}
	while (m --) {
		scanf("%d%d%d", &u, &v, &w);
		-- u, -- v;
		mp[u][v] = mp[v][u] = min(mp[u][v], w);
	}
	return true;
}

inline void init() {
	memset(dis, 0x3f, sizeof(dis));
	for (register int i(0); i < n; ++ i)
	for (register int s(1); s < 1 << n; ++ s) if (!(s & 1 << i)) {
		for (register int k(0); k < n; ++ k)
		if (s & 1 << k) dis[i][s] = min(dis[i][s], mp[i][k]);
	}
	for (register int i(1); i < 1 << n; ++ i) {
		register int s0((1 << n) - 1 ^ i);
		for (register int j(1); j < 1 << n; ++ j) if (!(i & j)){
			for (register int k(0); k < n; ++ k)
				if (j & 1 << k)
					if (dis[k][i] <= 1e9) gap[i][j] += dis[k][i];
					else {gap[i][j] = -1; break;}}
			else gap[i][j] = -1;
	}
}

inline int DP() {
	int ans(0x7ffffff);
	memset(f, 0x3f, sizeof(f));
	for (register int i(0); i < n; ++ i) f[0][1 << i] = 0;
	for (register int i(1); i < n; ++ i)
	for (register int S(1); S < 1 << n; ++ S) if (len[S] > i) {
		for (register int S0(S - 1 & S); S0; S0 = S0 - 1 & S)
			if (gap[S0][S ^ S0] != -1 && f[i - 1][S0] <= 1e9)
			f[i][S] = min(f[i][S], f[i - 1][S0] + i * gap[S0][S ^ S0]);
	}
	for (register int i(0); i < n; ++ i) ans = min(ans, f[i][(1 << n) - 1]);
	return ans;
}

int main() {
	if (!scan()) return 0;//不特判一下你就知道
	init();
	printf("%d", DP());
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值