计蒜客 圣诞树(堆优化dijkstra)

题目描述

圣诞节快到了,蒜头君准备做一棵大圣诞树。

这棵树被表示成一组被编号的结点和一些边的集合,树的结点从 1 到 n 编号,树的根永远是 1。每个结点都有一个自身特有的数值,称为它的权重,各个结点的权重可能不同。对于一棵做完的树来说,每条边都有一个价值 ve,若设这条边 e 连接结点 i 和结点 j,且 i 为 j的父结点(根是最老的祖先),则该边的价值ve=sj*we,sj表示结点 j 的所有子孙及它自己的权重之和,we表示边 e 的权值。

现在蒜头君想造一棵树,他有 m 条边可以选择,使得树上所有边的总价值最小,并且所有的点都在树上,因为蒜头君喜欢大树。

输入格式

第一行输入两个整数 n 和 m(0≤n,m≤50,000),表示结点总数和可供选择的边数。
接下来输入一行,输入 n 个整数,依次表示每个结点的权重。
接下来输入 m 行,每行输入 3 个正整数a,b,c(1≤a,b,≤n,1≤c≤10,000),表示结点 a 和结点 b 之间有一条权值为 c 的边可供造树选择。

输出格式

输出一行,如果构造不出这样的树,请输出No Answer,否则输出一个整数,表示造树的最小价值。

样例输入

4 4
10 20 30 40
1 2 3
2 3 2
1 3 5
2 4 1

样例输出

370

简要分析

这道题原本是求每条边的价值之和,但是根据公式 Ve=Sj × We 分析一下,就可以推导出

最后的总价值之和 =(顶点① 的权值× 顶点①到根①的边总权值和) + (顶点②的权值 × 顶点②到根①的边总权值和) + … + (顶点n的权值 × 顶点n到根①的边总权值和)

各个顶点的权值都是定了的,因此本题也就转化成求顶点1到各个顶点的最短路径

ac代码

#include<iostream>
#include<cstring>
#include<cstdio>
#include<set>
using namespace std;

const int MAX=50001;
int n,m;
struct edge{
    int v,w,next;
    edge(){}
    edge(int _v,int _w,int _n):v(_v),w(_w),next(_n){}
}e[MAX*2];
int w[MAX],p[MAX],dist[MAX];//存储点的权值,各点指向边的头指针,1到各点的距离
bool vst[MAX];//判断各点是否拜访
typedef pair<int,int> PII;
set<PII,less<PII> > min_heap;//存储点和点到1的距离的数据集的集合,并按照距离从小到大排序
bool dijkstra(int s){
    memset(vst,0,sizeof(vst));
    memset(dist,0x3f,sizeof(dist));
    dist[s]=0;//第一个点初始化为未拜访
    min_heap.insert(make_pair(0,s));//将起点放入集合
    for(int i=0;i<n;i++){
        if(min_heap.size()==0)
            return false;
        set<PII,less<PII> >::iterator iter=min_heap.begin();//取出集合中距离最小的点
        int u=iter->second;//存储距离最小的点的编号
        vst[u]=true;//标记为已访问
        min_heap.erase(*iter);//擦除这个点
        for(int j=p[u];j+1;j=e[j].next){//邻接表用法
            int v=e[j].v;
            if(!vst[v]&&dist[v]>dist[u]+e[j].w){//这个点未求出过最小距离,并且距离可更新
                min_heap.erase(make_pair(dist[v],v));//擦除原有数据
                dist[v]=dist[u]+e[j].w;//更新数据
                min_heap.insert(make_pair(dist[v],v));//放入新数据
            }
        }
    }
    return true;
}

int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)scanf("%d",&w[i]);
    memset(p,-1,sizeof(p));
    for(int i=0;i<2*m;){
        int u,v,c;
        scanf("%d%d%d",&u,&v,&c);
        e[i]=edge(v,c,p[u]);
        p[u]=i++;
        e[i]=edge(u,c,p[v]);
        p[v]=i++;
    }
    if(dijkstra(1)){
        long long sum=0;
        for(int i=1;i<=n;i++){
            sum+=dist[i]*w[i];
        }
        cout<<sum<<endl;
    }
    else
        cout<<"No Answer"<<endl;
    return 0;//give me five
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值