洛谷P3959 宝藏

洛谷P3959 宝藏

题目描述

参与考古挖掘的小明得到了一份藏宝图,藏宝图上标出了 \(n\) 个深埋在地下的宝藏屋, 也给出了这 \(n\) 个宝藏屋之间可供开发的\(m\) 条道路和它们的长度。
小明决心亲自前往挖掘所有宝藏屋中的宝藏。但是,每个宝藏屋距离地面都很远, 也就是说,从地面打通一条到某个宝藏屋的道路是很困难的,而开发宝藏屋之间的道路 则相对容易很多。
小明的决心感动了考古挖掘的赞助商,赞助商决定免费赞助他打通一条从地面到某 个宝藏屋的通道,通往哪个宝藏屋则由小明来决定。
在此基础上,小明还需要考虑如何开凿宝藏屋之间的道路。已经开凿出的道路可以 任意通行不消耗代价。每开凿出一条新道路,小明就会与考古队一起挖掘出由该条道路 所能到达的宝藏屋的宝藏。另外,小明不想开发无用道路,即两个已经被挖掘过的宝藏 屋之间的道路无需再开发。
新开发一条道路的代价是:

\[\mathrm{L} \times \mathrm{K} \]

L代表这条道路的长度,K代表从赞助商帮你打通的宝藏屋到这条道路起点的宝藏屋所经过的 宝藏屋的数量(包括赞助商帮你打通的宝藏屋和这条道路起点的宝藏屋) 。
请你编写程序为小明选定由赞助商打通的宝藏屋和之后开凿的道路,使得工程总代 价最小,并输出这个最小值。

输入输出格式

输入格式:

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

输出格式:

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

思路

这道题真的很难,我觉得是NOIP2017最难的一题
标准做法应该是状压dp
状态压缩的是已经打通了的宝藏屋的状态,用\(i\)表示,\(j\)表示当前所有节点中最大的深度。
可以与处理出每一个状态中所有已经探索了的宝藏屋全部在向下开发一层后得到的状态,记做\(Pair_j\)
\(F_{i,j}\)表示状态为\(i\)的情况下最大深度为\(j\)时所消耗的最小代价。
那么对于每一个状态\(i\),我们可以枚举他状态压缩后的子集\(k\),如果\(i\)是这个子集在向下拓展一层后的子集,即\(i\subseteq Pair_k\),那么说明,\(k\)中只需要向下拓展一层就可以得到\(i\),这个时候向下拓展时每一条边的费用\(l\times k\)中的\(k\)固定,也就是说可以使用贪心进行拓展,这时候就成立\(F_{i,j}=F_{k,j-1}+min{\sum{L\times K}}\)

CODE

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define MAXN 15
#define MAXM 1010
using namespace std;
const int MAXT =  (1<<MAXN);
int f[MAXT][MAXN],Pair[MAXT];
int mmap[MAXN][MAXN];
int i,j,k,m,n,u,v,w,final,INF,ans;
int main(){
    scanf("%d%d",&n,&m);
    memset(mmap,0x3f,sizeof(mmap));
    for(i=1;i<=m;i++){
        scanf("%d%d%d",&u,&v,&w);
        u--,v--;
        mmap[u][v]=mmap[v][u]=min(mmap[u][v],w);
    }
    memset(f,0x3f,sizeof(f));
    INF=f[0][0];
    final=(1<<n)-1;
    for(i=1;i<=final;i++){
        for(j=0;j<n;j++)
            if(((1<<j)|i)==i){
                mmap[j][j]=0;
                for(k=0;k<n;k++){
                    if(mmap[j][k]!=INF){
                        Pair[i]|=1<<k;
                    }
                }
            }
    }
    for(i=0;i<n;i++) f[1<<i][0]=0;
    for(i=2;i<=final;i++){
        for(int s0=i;s0;s0=(s0-1)&i)
            if((Pair[s0]|i)==Pair[s0]){
                int sum=0;
                int deltas=s0^i;
                for(k=0;k<n;k++)
                    if((1<<k)&deltas){
                        int tmp=INF;
                        for(j=0;j<n;j++)
                            if((1<<j)&s0){
                                tmp=min(tmp,mmap[j][k]);
                            }
                        sum+=tmp;
                    }
                for(j=1;j<n;j++)
                    if(f[s0][j-1]!=INF)
                        f[i][j]=min(f[i][j],f[s0][j-1]+sum*j);
            }
    }
    ans=INF;
    for(i=0;i<n;i++) ans=min(ans,f[final][i]);
    printf("%d\n",ans);
    return 0;
}
posted @ 2019-02-02 00:28  EternalBlue  阅读(177)  评论(0编辑  收藏  举报