poj-3013 透过现象看本质,其实都一样

这道题目咋一看就是最小生成树啊。

题目描述大概是这样:

KCm要准备一颗圣诞树,这棵树有一些节点和边组成。节点从1到n,根总是1.每个节点都有自己的总量,而边的价格是由边的单价乘以子孙节点的重量。

求出这么一颗有n个节点的树,使花费最小。

 

很像最小生成树,仔细研究,没法这样做。因为每条边在找到这颗树之前很难计算它的价值。

因为本来你算好的边的价值,但是在去掉某些边时,会导致边的价值发生变化,那怎么办。。。。

 

这道题目开始我也是不知道怎么计算,看到discuss说是最短路。试想最后已经得到一个树,而每条边的价值就是子孙节点重量和乘以单价,可以转换为,此节点重量乘以从根到本节点的最短路,这么一转化,问题瞬间变得简单了。

 

那么所有点的最短路径一定是一棵树吗?   这个是显然的 ,因为如果形成环了,到达某个点就出现了多个路径,就要删去长的那条,使其没有环

 

样例中的计算方法
4*40+3*50+2*60+3*(20+40+50+60)+2*30+1*(10+20+30+40+50+60)
=10*1+20*(1+3)+30*(2+1)+40*(4+1+3)+50*(3+1+3)+60*(1+2+3)
=10+80+90+320+350+360
=1210

 

题目要注意几个地方

1、n=0或者1, 答案是0,而不是no answer

2、数据范围大,要用long long

3、n=0或者1的时候也要将数据读入,不能直接输出0就不管输入了,否则会re

4、注意是无向图,建邻接表的时候要双向

5、数据量大,邻接矩阵不行,必须邻接表

 

搞清楚了这些,剩下就是简单的spfa了

代码:

#include<stdio.h>

#define maxN 50005
#define inf 10000000000000

struct EDGE 
{
	int v,w;
	int next;
}edge[2 * maxN];//邻接表

int preEdge[2 * maxN];//preEdge[u],上一条以u为起点的边在edge中的位置
long long dis[maxN];//最短路
bool vis[maxN];
int weight[maxN];//节点重量
int queue[20 * maxN];//松弛队列
int n,m;

void Init()//初始化
{
	for (int i = 1; i <= n; ++ i)
	{
		preEdge[i] = 0;
		dis[i] = inf;
		vis[i] = false;
	}
}

void spfa()
{
	int head = 0, tail = 1;
	queue[0] = 1;
	dis[1] = 0;
	while (head < tail)
	{
		int u = queue[head];
		vis[u] = true;
		int p = preEdge[u];//以u为起始点 邻接表中第几条边
		while (p != 0)
		{
			int v = edge[p].v, w = edge[p].w;
			if (dis[v] > dis[u] + w)
			{
				dis[v] = dis[u] + w;
				if (!vis[v])
				{
					vis[v] = true;
					queue[tail] = v;
					tail ++;
				}
			}
			p = edge[p].next;//下一条以u起点的边
		}
		head ++;
		vis[u] = false;
	}
	bool flag = true;
	long long  sum = 0;
	for (int i = 2; i <= n; ++ i)
	{
		if (dis[i] == inf)
		{
			flag = false;
			break;
		}
		sum += weight[i] * dis[i];
	}
	if (!flag)
	{
		printf("No Answer\n");
	}
	else
		printf("%lld\n", sum);


}

int main()
{
	int t;
	scanf("%d", &t);
	while (t --)
	{
		scanf("%d%d", &n, &m);
		for (int i = 1; i <= n; ++ i)
		{
			scanf("%d", &weight[i]);
		}
		Init();
		int index = 1;
		for (int i = 1; i <= m; ++ i)
		{
			int a, b, c;
			scanf("%d%d%d", &a, &b, &c);
			edge[index].v = b;
			edge[index].w = c;
			edge[index].next = preEdge[a];
			preEdge[a] = index ++;

			edge[index].v = a;
			edge[index].w = c;
			edge[index].next = preEdge[b];
			preEdge[b] = index ++;

		}
		if (n == 0 || n == 1)
		{
			printf("0\n");
			continue;
		}
		spfa();
	}
	return 0;
}


 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值