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;
}