NOIP2017 宝藏

题目链接

Solution

看到数据范围 (n≤12),考虑状压 dp。数据有重边,使用邻接矩阵建图。

(f_{i,S}) 表示第 (i) 层,当前放入树中的点状态是 (S)(1) 表示放了,(0) 表示没放)时,最小的代价。则有转移方程:

[f_{i,S}=f_{i-1,S'}+connect(S xor S',S') * i ]

其中 S' 是 S 的一个子集,代表上一层的状态,而 connect(S xor S',S') 表示把在 S 中出现但未在 S' 中出现的点与树上的节点(S')连接起来需要的代价。考虑到时间复杂度,S' 用枚举子集的方式转移。

那么怎么计算这个代价呢?可以考虑一种最暴力的方式:对于每个待连接的点,用它与树上节点之间权值最小的边连接。

但是现在又有一个问题:如果这条边连接的不是第 i-1 层的点,而是更靠前的点怎么办?比如这样:

(黑点表示已经在树上的点,红点表示待连接的点)

红点找到的边连接的是上一层的某一个点,这会改变红点应有的深度。但是仔细想想,显然当红点放在第 i-1 层时会更优,这种情况对最优解不会产生影响。

因此我们就得到了一个 (O(n^33^n)) 的写法。因为人菜自带大常数,需要吸氧才能通过。

Code

// O2
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define LL long long
using namespace std;

const LL N = 2333333;
LL n, m, cnt = 0, ans = 0x3f3f3f3f, head[N], vis[N], e[66][66];
LL f[15][666666];

LL connect(LL pos, LL S)
{
    LL res = 0x3f3f3f3f;
    for(int i = 0; i < n; i++)
        if(S & (1 << i)) res = min(res, e[pos][i + 1]);
    return res;
}

int main()
{
    scanf("%lld%lld", &n, &m);
    memset(e, 0x3f, sizeof(e));
    memset(head, 0, sizeof(head));
    for(LL u, v, w, i = 1; i <= m; i++)
    {
        scanf("%lld%lld%lld", &u, &v, &w);
        e[u][v] = min(e[u][v], w);
        e[v][u] = min(e[v][u], w);
    }
    memset(f, 0x3f, sizeof(f));
    for(LL i = 0; i < n; i++) f[0][1 << i] = 0;
    for(LL i = 1; i < n; i++)
    for(LL j = 0; j < (1 << n); j++)
    for(LL k = j; k; k = ((k - 1) & j))
    {
        LL s = j ^ k, res = 0, fl = 1;
        for(int h = 1; h <= n; h++)
            if(s & (1 << (h - 1)))
            {
                int now = connect(h, k);
                if(now == 0x3f3f3f3f) { fl = 0; break; }
                res += now;
            }
        if(fl) f[i][j] = min(f[i][j], f[i - 1][k] + res * i);
    }
    for(LL i = 0; i <= n; i++) ans = min(ans, f[i][(1 << n) - 1]);
    printf("%lld", ans);
    return 0;
}
原文地址:https://www.cnblogs.com/Andy-park/p/13776736.html