宝藏

https://www.acwing.com/problem/content/531/

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

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

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

在此基础上,小明还需要考虑如何开凿宝藏屋之间的道路。

已经开凿出的道路可以任意通行不消耗代价。

每开凿出一条新道路,小明就会与考古队一起挖掘出由该条道路所能到达的宝藏屋的宝藏。

另外,小明不想开发无用道路,即两个已经被挖掘过的宝藏屋之间的道路无需再开发。

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

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

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

输入格式

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

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

输出格式

输出共一行,一个正整数,表示最小的总代价。

数据范围

1≤n≤12

,
0≤m≤1000,
v≤5∗105

 

输入样例:

4 5 
1 2 1 
1 3 3 
1 4 1 
2 3 4 
3 4 1 

输出样例:

4

注意

本题数据有加强,前二十个测试点为NOIP官方数据,后三个测试点为加强数据。

 

【解析】:

相当于求一棵有根生成树的最小代价。

考虑状压DP,f[i][j]表示生成集合为i, 最大深度为j的树所需的最小代价。

f[i][j] = min{f[s][j - 1] + cost * j},s为i的子集,转移代价为所有第j层的点向s集合中的点连边的最小值的和 * 深度。

但是当第j层的点向s集合中的点连边取最小值,连向的点不一定出现在第j - 1,但是这不会影响最后答案:

考虑这种转移方式的2个性质:

1,最优解(最小值)一定会被计算到,且计算结果一定完美。

2,非完美的计算使得总代价偏大,故不会影响到最小值。

#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#define f(i,l,r) for(i=(l);i<=(r);i++)
using namespace std;
const int MAXN = 12, INF = 0x3f3f3f3f;
int n, m;
int d[MAXN][MAXN];
int f[1 << MAXN][MAXN], g[1 << MAXN][MAXN];
int S[MAXN], all;
int ans = INF;
void init()
{
    int i, j, k;
    f(i, 0, n - 1){
        f(j, 0, n - 1){
            if(i == j || d[i][j] == INF) continue;
            f(k, 0, all){
                g[k | (1 << j)][i] = min(g[k | (1 << j)][i], d[i][j]);
            }
        }
    }
    return;
}
int main()
{
    memset(d, 0x3f, sizeof d);
    memset(f, 0x3f, sizeof f);
    memset(g, 0x3f, sizeof g);
    int i, j, k;
    cin >> n >> m;
    all = (1 << n) - 1;
    f(i, 1, m){
        int u, v, w;
        cin >> u >> v >> w;
        u--;
        v--;
        d[u][v] = min(d[u][v], w);
        d[v][u] = d[u][v];
    }
    init();
    f(i, 1, all){
 //       cout << i << " " << g[i][1] << endl;
    }
    f(i, 0, n - 1){
        f[1 << i][0] = 0;
    }
    f(i, 1, all - 1){
        f(j, 0, n - 2){
            int S  = all ^ i;
            int sub = S;
            do{
                int w = 0, flag = 1;
                f(k, 0, n - 1){
                    if(sub & (1 << k)){
                        w += g[i][k];
                        if(g[i][k] == INF){
                            flag = 0;
                            break;
                        }
                    }
                }
                if(flag){
                    f[i | sub][j + 1] = min(f[i | sub][j + 1], f[i][j] + w * (j + 1));
                }
                sub = (sub - 1) & S;
            }while(sub);
        }
    }
    f(i, 0, n - 1){
        ans = min(ans, f[all][i]);
    }
    cout << ans << endl;
    return 0;
}

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值