luogu_P3959 题解

蒟蒻的第一题状压DP紫题。

题目分析

题目转化为在一个有权图上,求一颗生成树,并选择一个根 \(rt\),使 \(\sum_u w(u, fa_u) \times (dep_u - 1)\) 最小,其中 \(dep_{rt} = 1\)

\(f_{i, S}\) 表示进行到深度为 \(i\),已经加入树的集合为 \(S\)

我们尝试列一下转移方程,\(f_{i, S \cup X} = \min \{ f_{i - 1, X} + (i - 1)g_{X, S} \}\),其中 \(S \cap X = \varnothing\),即 \(S \subseteq X^c\) (这一点非常有用,可以将利用子集枚举将复杂度从 \(O(2^{2n})\) 优化为 \(O(3^n)\))。\(g_{X, S}\) 表示已经加入树的集合为 \(X\),要将 \(S\) 加入的最小花费。

不知道大家设状态时有没有怀疑,这种状态设计好像会将不合法的状态加入,好像会加入不是同一深度的点——

吗?

让我们仔细想想,既然不是同一深度,那肯定是比当前深度更小的位置,但是更优的方案是先加入这些深度更小的点。换言之,这些非法的转移答案只会更劣!!!所以我们完全不需要再开一个状态,我们放着不管即可。

接下来考虑如何计算 \(g_{X, Y}\)。注意为了与上面统一,这里的 \(X, Y\) 也满足 \(Y \subseteq X^c\)。那么最简单的方法肯定就是一个一个遍历两个集合中的点计算,比较简单,不多赘述。但是我们是否可以单独计算一个点然后递推?

这时,\(\operatorname{lowbit}\) 出手!因为 \(\operatorname{lowbit}\) 相当于把集合中的最后一个点给拎出来了,那么我们就可以用 \(g_{X, Y - \operatorname{lowbit}(Y)}\) 再加上 \(\operatorname{lowbit}(Y)\) 所代表的点的最小代价来得到。这个方法来自 FlashHu 在洛谷题解中的方法,在这仅为整理,向 FlashHu 巨佬表示敬佩!

那么最后的时间复杂度就为 \(O(n3^n)\)

Code

#include <bits/stdc++.h>
using namespace std;

#define int long long
#define lowbit(x) (x & (-x))

inline int read(){
	int x = 0, f = 1;
	char c = getchar();
	for(; c < '0' || c > '9'; c = getchar())
		if(c == '-') f = -1;
	for(; c >= '0' && c <= '9'; c = getchar())
		x = (x << 3) + (x << 1) + (c ^ 48);
	return f * x;
}

inline void write(int x){
	if(x == 0) putchar('0');
	if(x < 0) putchar('-'), x = -x;
	static int sta[40];
	int top = 0;
	while(x){
		sta[++top] = x % 10;
		x /= 10;
	}
	while(top) putchar(sta[top--] + '0');
}

constexpr int INF = 1e15;//注意极值设置不能太大
constexpr int N = 17;
constexpr int M = (1 << 12) + 5;
int mp[N][N], LOG2[M], pre[M], f[N][M], g[M][M];
int n, m, U;
int ans = INF;

void init(){
	U = (1 << n) - 1;
	LOG2[0] = -1;
	for(int i = 1; i <= U; i++) LOG2[i] = LOG2[i >> 1] + 1;
	for(int i = 1; i <= n; i++){
		for(int j = 0; j <= U; j++) f[i][j] = INF;
		for(int j = 1; j <= n; j++) mp[i][j] = INF;
	}
	for(int i = 0; i <= U; i++)
		for(int j = 0; j <= U; j++) g[i][j] = INF;
}

signed main(){
	n = read(), m = read();
	init();
	for(int i = 1; i <= m; i++){
		int u = read(), v = read(), w = read();
		if(mp[u][v] <= w) continue;
		mp[u][v] = w;
		mp[v][u] = w;
	}

	for(int i = 1; i <= U; i++){
		g[i][0] = 0;
		int j = U & (~i);
		int p = 0;
		for(int s = j; s; s = (s - 1) & j){
			pre[s] = p;
			p = s;
		}
		for(; p; p = pre[p]){//要从小到大递推
			int u = LOG2[lowbit(p)] + 1;
			int c = INF;
			for(int v = 1; v <= n; v++) if(i & (1 << (v - 1)))
				c = min(c, mp[v][u]);
			g[i][p] = min(g[i][p], g[i][p - lowbit(p)] + c);//lowbit妙用
		}
	}

	for(int i = 1; i <= n; i++) f[1][1 << (i - 1)] = 0;
	ans = min(ans, f[1][U]);
	for(int i = 2; i <= n; i++){
		for(int j = 0; j <= U; j++){ //已经加入树的集合
			int m = U & (~j);
			for(int s = m; s; s = (s - 1) & m){ //新加入的点
				f[i][j | s] = min(f[i][j | s], f[i - 1][j] + (i - 1) * g[j][s]);
		}
		ans = min(ans, f[i][U]);
	}
	write(ans);
	return 0;
}

posted @ 2025-08-24 18:05  dairuize  阅读(6)  评论(0)    收藏  举报