【NOIP2017 提高组】宝藏

【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;
}
posted @ 2022-09-29 14:05  azzc  阅读(39)  评论(0编辑  收藏  举报