C. 点权

做点权要冲点券~~~

一. 题意

给出节点数为 n 的一棵树,每条边有边权 c
一个点必须由两个相邻的已更新的点更新,花费为两条边的边权之和。
入度小于2的节点(叶子节点)已更新。
对于每个点,求出能够将其更新的最小花费。如不可更新,输出-1。

二. 思路        

         我们要跳出一个圈子:树本身就是个图

        其实知道这个就很好办了:

                我们依次枚举与该节点相邻的节点,用临节点更新它。具体而言,先将他变为一,再将它变为二。注意每次更新后它也就能更新与其相邻的节点了。我们可以用一个堆存,这样每次取出来的

        更多细节请看代码注释。

#include<queue>
#include<iostream>
#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=a;i<=(b);++i)
using namespace std;
	
const int N=1e5+1001,INF=0x3f3f3f3f;
int x,y,z,w;
bool st[N];
int n,root,g[N],d[N][2],h[100005*2],idx;
 
//邻接表存图 
struct Edge{
	int next,to,dis;
}edge[100005*2];
void add(int u, int v, int w)
{
	edge[++idx].next=h[u];
	edge[idx].to=v;
	edge[idx].dis=w;
	h[u]=idx;
}
 
struct node{
	//x是编号,dist是代价 
	int x,dist; 
};priority_queue<node>q;
// 我们用的是堆,这样每次取出的一定是最小的节点,产生的答案也一定是最优的。 
bool operator < (node x,node y)
{	
	// 重载小于号 
	return x.dist>y.dist;
}
 
void spfa()
{
	//这里用的是 SPFA 的思想,DIJ 的外表, 
	//  脑袋需要转弯 
	memset(d,0x3f3f3f3f,sizeof d);
	//方法:
	//先变成 1 ,再变成 2 
	
	//先把叶子结点入堆 
	for(int i=1;i<=n;i++)
	{
		if(g[i]<2)
		{
			q.push({i,d[i][1]=0});
		}
	}
	
	while(!q.empty())
	{
		//x为取出当前节点
		x=q.top().x; q.pop();
		
		// dist[i][0]:变为 1 所要的代价
		// diat[i][1]:变为 2 所要的代价 
		if(y>d[x][1]) continue;
		
		//遍历与其相邻的节点 
		for(int i=h[x];i;i=edge[i].next)
		{
			y=edge[i].to,w=edge[i].dis;
			//更新变为1的代价 
			//更新为1后,它还能去更新其他节点 
			if(d[y][1]>d[x][1]+d[y][0]+w)
			{
				d[y][1]=d[x][1]+d[y][0]+w;
				q.push({y,d[y][1]});
			}
			//更新变为2的代价 
			//更新成二后,便不能去更新相邻节点了。 
			if(d[y][0]>d[x][1]+w)
			{
				d[y][0]=d[x][1]+w;
			}
		}
	}
}
	
signed main()
{		
	
	cin>>n;
	rep(i,1,n-1)
	{
		cin>>x>>y>>z;
		//g数组存的是度 
		g[x]++,g[y]++;
		add(x,y,z); add(y,x,z);
	}
		
	spfa();
	
	rep(i,1,n)
	{ 
		//判断当前节点是否被更新过 (是否等于INF) 
		if(d[i][1]!=INF)
			cout<<d[i][1]<<" ";
		//如果代价是 正无穷(INF),说明无法更新,输出-1 
		else
			cout<<"-1"<<" ";
	}
	
	return 0;
}	

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值