1. 程式人生 > 其它 >NOIP2017寶藏

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<bits/stdc++.h>
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 <typename T>
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;
}