zoukankan      html  css  js  c++  java
  • 【题解】P3959 宝藏

     P3959 宝藏

    题目描述

    参与考古挖掘的小明得到了一份藏宝图,藏宝图上标出了 n 个深埋在地下的宝藏屋, 也给出了这 n 个宝藏屋之间可供开发的 条道路和它们的长度。

    小明决心亲自前往挖掘所有宝藏屋中的宝藏。但是,每个宝藏屋距离地面都很远, 也就是说,从地面打通一条到某个宝藏屋的道路是很困难的,而开发宝藏屋之间的道路 则相对容易很多。

    小明的决心感动了考古挖掘的赞助商,赞助商决定免费赞助他打通一条从地面到某 个宝藏屋的通道,通往哪个宝藏屋则由小明来决定。

    在此基础上,小明还需要考虑如何开凿宝藏屋之间的道路。已经开凿出的道路可以 任意通行不消耗代价。每开凿出一条新道路,小明就会与考古队一起挖掘出由该条道路 所能到达的宝藏屋的宝藏。另外,小明不想开发无用道路,即两个已经被挖掘过的宝藏 屋之间的道路无需再开发。

    新开发一条道路的代价是:L × K

    L代表这条道路的长度,K代表从赞助商帮你打通的宝藏屋到这条道路起点的宝藏屋所经过的 宝藏屋的数量(包括赞助商帮你打通的宝藏屋和这条道路起点的宝藏屋) 。

    请你编写程序为小明选定由赞助商打通的宝藏屋和之后开凿的道路,使得工程总代 价最小,并输出这个最小值。

    输入格式

    第一行两个用空格分离的正整数 n,m,代表宝藏屋的个数和道路数。

    接下来 m 行,每行三个用空格分离的正整数,分别是由一条道路连接的两个宝藏 屋的编号(编号为 1n),和这条道路的长度 v

    输出格式

    一个正整数,表示最小的总代价。


    Solution

    这道题和 灰原 一起想了一下午还是没有想出来(我们果然还是太菜了),去某谷看了这篇题解,觉得写的很棒想法也很巧,想记录一下这道题来加深一下记忆。

    我们已经想到的:1.用一个数来表示走过的点的集合

            2.往这个集合里面加一个点时,枚举此集合里的所有点,取

             代价最小的连边

    卡住我们的点是:1.我们无法证明 当往一个集合里新添一个点的时候,只用在

             原来的连边方式上新连一条边即为最优解

            2.无法仅通过一个集合来计算出代价,因为代价中含有边的

             权值和两点之间的距离(或者说是在树上的两点之间的高

             度

    题解比我们多想的是:1.引进了一个新的变量-树的高度,这样的好处是,可以

                 方便得出代价

              2.在转移时,对于一个已经走了了 i 个点的集合,不一

                 定仅从走了 i - 1 个点的集合转移过来,可以一次性从

                 i 的子集转移过来,加很多点

              3.正因为一次性可以加多个点,那么每一次我们都是

                 添的点全部与高度最高的点连这样子得出的状态不

                 一定是正确的,但是正确的答案一定会被算出来(原

                 因请看4.)

              4.我们假设有集合 i高度为 h,由集合 j 转移过来,

                 此时需要在 j 里面新添 k 个点。我们先用枚举的方

                 法,求出sum 表示 k 个点与集合里的点的最短边之

                 和,然后将 sum 乘以 h,得出代价。很明显的可以

                 看出,k 个点,每个点向集合里连的最短边,不一定

                 是高度为 h 的点,所以这个状态所存的代价不一定是

                 正确的。但是,总有一个树的高度 h0 可以满足,这

               k 个点,所连的最短边相对应的另外 k 个点,全部是

                高度为 h0 的点(可以自己 举具体的例子一步一步往

                前推),则f[i][h0]所存的代价是正确的,而且是最优

                的,这样子原来的 f[i][h] 所存的,虽然不是正确的,

              但它一定不是最优的,所以取答案时绝对不会取到它

    Code

    #include<bits/stdc++.h>
    #define F(i, x, y) for(int i = x; i <= y; ++ i)
    using namespace std;
    int read();
    const int S = (1 << 12);
    const int N = 15;
    const int inf = 0x3f3f3f3f;
    int n, m, ans = inf, all;
    int u, v, e;
    int d[N][N]; //直接用邻接矩阵存加快访问速度
    int s[S]; //预处理出对于集合 i 此时还可以往外连那些点
    int f[S][N]; //f[i][j]表示集合 i 高度为 j 时的代价(其实也许是个假的233)
    int main()
    {
        n = read(), m = read(), -- n, all = (1 << n + 1) - 1; //为了方便 将点设为 0 ~ n - 1;all 为总状态数
        F(i, 0, n) F(j, 0, n) d[i][j] = inf;
        F(i, 0, all) F(j, 0, n) f[i][j] = inf;
        F(i, 0, n) f[(1 << i)][0] = 0; //因为可以自选起点,所以这些状态是合法的
        F(i, 1, m) 
        {
            u = read(), v = read(), e = read();
            -- u, -- v, d[u][v] = d[v][u] = min(d[u][v], e); //最多给了1000条边,而实际上最多只有72条,取最短的
        }
        F(i, 1, all)
            F(j, 0, n)
                if((i | (1 << j)) != i) 
                    F(k, 0, n) 
                        if((i | (1 << k)) == i && d[j][k] != inf)
                            s[i] |= (1 << j); //预处理出 s[]
        F(i, 1, all)
            for(int s0 = (i - 1) & i; s0; s0 = (s0 - 1) & i) //保证 s0 是 i 的子集
                if((s0 | s[s0] | i) == (s[s0] | s0)) //判断 s0 是否可以经过连边变成 i
                {
                    int sum = 0; //求出每条边的权值
                    F(k, 0, n)
                        if(((1 << k) | i) == i && ((1 << k) | s0) != s0) //如果 k 不属于 s0 而属于 i,则需要新连边
                        {
                            int tmp = inf;
                            F(h, 0, n)
                                if(((1 << h) | s0) == s0) //如果 h 属于 s0 则可以连边
                                    tmp = min(tmp, d[h][k]);
                            sum += tmp;
                        }
                    F(j, 1, n) 
                        if(f[s0][j - 1] != inf)
                            f[i][j] = min(f[i][j], f[s0][j - 1] + sum * j); //最关键的地方,一定要弄懂!!
                }
        F(i, 0, n) ans = min(ans, f[all][i]); //在全集中的每个高度中找最小值
        printf("%d
    ", ans);
        return 0;
    }
    int read()
    {
        int x = 0;
        char c = getchar();
        while(c < '0' || c > '9') c = getchar();
        while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
        return x;
    }
  • 相关阅读:
    Hdu 5396 Expression (区间Dp)
    Lightoj 1174
    codeforces 570 D. Tree Requests (dfs)
    codeforces 570 E. Pig and Palindromes (DP)
    Hdu 5385 The path
    Hdu 5384 Danganronpa (AC自动机模板)
    Hdu 5372 Segment Game (树状数组)
    Hdu 5379 Mahjong tree (dfs + 组合数)
    Hdu 5371 Hotaru's problem (manacher+枚举)
    Face The Right Way---hdu3276(开关问题)
  • 原文地址:https://www.cnblogs.com/Bn_ff/p/12160569.html
Copyright © 2011-2022 走看看