zoukankan      html  css  js  c++  java
  • 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;
    }
    
  • 相关阅读:
    洛谷P3258 [JLOI2014]松鼠的新家
    洛谷P1306 斐波那契公约数
    bzoj4247:挂饰
    [YTU]_2354 (H 实现复数类中的加运算符重载【C++运算符重载】)
    [YTU]_2440 (C++习题 复数类--重载运算符+,-,*,/)
    [YTu]_2441 (C++习题 复数类--重载运算符2+)
    [YTu]_2439(C++习题 复数类--重载运算符+)
    10.3 重载双目运算符
    10.2.1 关于vc++不支持把类的成员函数定义为类的友元函数的处理
    10.2 运算符重载函数作为类成员函数和友元函数
  • 原文地址:https://www.cnblogs.com/Andy-park/p/13776736.html
Copyright © 2011-2022 走看看