做点权要冲点券~~~
一. 题意
给出节点数为 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;
}