NOIP2017宝藏


题意

link

给出 \(n\) 个点 \(m\) 条边的图,选一个点作为根,每选一个点的价值是 \(\text{dep} \times w\), 即深度(从0开始)乘边权,求生成一棵树的最小价值。

状压dp

状态

考虑到深度不好压缩,那就按照深度小到大往里加点,即统一深度的点一起放进去,这样深度就相同了。

\(f[dep][S]\) 表示现在深度是 \(dep\), 选了集合 \(S\) 中的点的最小价值。

枚举未加入的点,但是不知道它能接在哪些加入的点下面,怎么办?再多记一维状态就会空间爆炸。

其实只要对每个选出的点连入已经加入的点就好,并且选最小的边权。

因为如果深度不是当前的 \(dep\), 那么一定存在一种方案 \(dep\) 会更小,那么就更优。

初始和最终状态

\(f[0][2^i] = 0\) 表示选一个根。
其它都是 无穷大。

最后枚举深度, 就是最小的\(f[dep][U]\)

转移

\[f[dep][S] = \min_{T \cap S = \emptyset} \{ f[dep - 1][T] + \sum_{i \in T}w(i, S)\} \]

其中 \(w(i, S)\) 表示点 \(i\) 连入 \(S\) 中的点的最小花费,预处理即可。

分析

预处理枚举子集和枚举连入的边,时间是 \(O(n2^n)\)

转移枚举子集和补集再求和,总的时间复杂度是 \(O(n^23^n)\)

代码

#include
using namespace std;

using ll = long long;
const int MAXN = 15;
const int INF = 0x3f3f3f3f;
//const int mod = 1000000007;
int mod; 
const double eps = 1e-9; 

template 
void Read(T &x) {
	x = 0; T f = 1; char a = getchar();
	for(; a < '0' || '9' < a; a = getchar()) if (a == '-') f = -f;
	for(; '0' <= a && a <= '9'; a = getchar()) x = (x * 10) + (a ^ 48);
	x *= f;
}

inline int add(const int &a, const int &b) { 
	static int c;
	c = a + b;
	if (c >= mod) c -= mod;
	if (c < 0) c += mod;
	return c;   
} 
inline int mul(const int &a, const int &b) {
	return 1ll * a * b % mod; 
}
int qpow(int a, int b) {
	int sum(1);
	while(b) {
		if (b & 1) sum = mul(sum, a);
		a = mul(a, a);
		b >>= 1;
	}
	return sum; 
}

int n, m;
int val[MAXN][MAXN], dis[MAXN][ (1 << MAXN) + 10];

int f[MAXN][ (1 << MAXN) + 10]; 
int main() {
	memset(val, 0x3f, sizeof(val));
	cin >> n >> m;
	for (int i = 1; i <= m; i ++) {
		int u, v, w;
		cin >> u >> v >> w;
		val[u][v] = val[v][u] = min(val[u][v], w); 
	}
	memset(dis, 0x3f, sizeof(dis)); 
	for (int i = 1; i <= n; i ++)
		for (int j = 1; j < (1 << n); j ++)
			for (int k = 0; k < n; k ++)
				if (j & (1 << k)) 
					dis[i][j] = min(dis[i][j], val[i][k + 1]); 
	
	memset(f, 0x3f, sizeof(f));
	for (int i = 1; i <= n; i ++) 
		f[0][1 << i - 1] = 0;
	int U = (1 << n) - 1; 
	for (int i = 1; i < n; i ++) 
		for (int j = 1; j < (1 << n); j ++)	{
			int now = U ^ j; 
			if (f[i - 1][now] == INF)
				continue;
			for (int k = j; k; k = j & (k - 1)) {
				int sum = 0; bool p = 0;
				for (int x = 0; x < n; x ++)
					if (k & (1 << x)) {
						if (dis[x + 1][now] == INF) {
							p = 1; break;
						} else 
							sum += dis[x + 1][now] * i; 
					}
				if (p) continue;
				f[i][now | k] = min(f[i][now | k], f[i - 1][now] + sum);	
			} 
		}
	int ans = INF;
	for (int i = 0; i < n; i ++)
		ans = min(ans, f[i][U]);
	cout << ans; 
	return 0;
}