【NOIP2017 提高组】宝藏
f[S][i]表示集合S的点构成的生成树,树高为i({i}为0)的最小花费
转移: 枚举S的子集S'为高度为i-1的树(S'可扩展出S):f[S][i]<-f[S'][i]+cost
预处理出g[S]表示S能够扩展出的点
点击查看代码
#include <stdio.h>
#include <string.h>
#include <algorithm>
using std::min;
int n, m;
int g[13][13];
int e[(1 << 12) + 1]; // 这个集合的点能够扩展出来的点
int f[(1 << 12) + 1][13];
int main() {
scanf("%d%d", &n, &m);
for(int i = 0; i < n; i ++) memset(g[i], 0x3f, n << 2), g[i][i] = 0;
for(int i = 0, a, b, c; i < m; i ++)
scanf("%d%d%d", &a, &b, &c), a --, b --, g[a][b] = g[b][a] = min(g[a][b], c);
for(int s = 1; s < (1 << n); s ++) // 预处理
for(int i = 0; i < n; i ++) if(s >> i & 1)
for(int j = 0; j < n; j ++) if(g[i][j] < 0x3f3f3f3f) e[s] |= 1 << j;
for(int s = 0; s < (1 << n); s ++) memset(f[s], 0x3f, sizeof(f[s]));
for(int i = 0; i < n; i ++) f[1 << i][0] = 0;
for(int s = 1; s < (1 << n); s ++)
for(int s2 = s; s2; s2 = (s2 - 1) & s) // s2为s的子集
if((e[s2] & s) == s) { // s可由s2扩展而来
int cost = 0, s3 = s ^ s2; // 最小(扩展)花费
for(int i = 0, tmp; i < n; i ++) if(s3 >> i & 1) {
tmp = 0x3f3f3f3f; // 扩展到i的点的最小花费
for(int j = 0; j < n; j ++) if(s2 >> j & 1) tmp = min(tmp, g[j][i]);
cost += tmp;
}
for(int k = 1; k < n; k ++) f[s][k] = min(f[s][k], f[s2][k - 1] + cost * k);
}
int res = 0x3f3f3f3f;
for(int i = 0; i < n; i ++) res = min(res, f[(1 << n) - 1][i]);
printf("%d\n", res);
return 0;
}