题意:给n个点从1到n标号,下面一行是每个点的权,另外给出m条边,下面是每条边的信息,两个端点+权值,边是无向边。你的任务是选出一些边,使这个图变成一棵树,使得这棵树的花费最小。这棵树的花费是这样算的,1号固定为树根,树中每个双亲节点下面的边都有个单价(即边权),然后单价乘上这条边的下面所有的子孙后代的点权和。
思路:通过写出求和式子可以发现求的其实是对每个点的权值乘以从节点1到这个点的最短路的最小值,通过简单的贪心证明可以发现每个点的最优情况就是从节点1到各个点的最短路长度。
此题注意:1、距离数组要用long long,而正无穷也要是long long下的正无穷。
2、n为0的情况。
#include <cstdio> #include <cstring> using namespace std; #define N 50005 #define INF 0x3fffffffffffffff struct edge{ int y,next; long long w; }e[N<<1]; int first[N],used[N],q[N<<1]; long long num[N],dis[N]; int n,m,T,top,k; void add(int x,int y,long long w){ e[top].y = y; e[top].w = w; e[top].next = first[x]; first[x] = top++; } int relax(int x,int y,long long w){ if(dis[x] + w < dis[y]){ if(dis[y] == INF) k--; dis[y] = dis[x]+w; return 1; } return 0; } int spfa(){ int i,j,front,rear,now; memset(used, 0, sizeof(used)); for(i = 1;i<=n;i++) dis[i] = INF; front = rear = -1; q[++rear] = 1; used[1] = 1; dis[1] = 0; while(front < rear){ now = q[++front]; used[now] = 0; for(j = first[now];j!=-1;j=e[j].next) if(relax(now,e[j].y,e[j].w) && !used[e[j].y]){ q[++rear] = e[j].y; used[e[j].y] = 1; } } return !k; } int main(){ scanf("%d",&T); while(T--){ int i,j,a,b; long long w,res=0; top = 0; memset(first, -1, sizeof(first)); scanf("%d %d",&n,&m); if(!n){ printf("0\n"); continue; } k = n-1; for(i = 1;i<=n;i++) scanf("%lld",&num[i]); for(i = 1;i<=m;i++){ scanf("%d %d %lld",&a,&b,&w); add(a,b,w); add(b,a,w); } j = spfa(); if(j){ for(i = 2;i<=n;i++) res += num[i]*dis[i]; printf("%lld\n",res); }else printf("No Answer\n"); } return 0; }