2022“杭电杯”中国大学生算法设计超级联赛(5) 1003 Slipper个人题解

1003 Slipper

题意

给出一颗有n个点的树,共有n - 1 条边,树的根节点是1,每两个点之间的一条边都有一个权值。现在可以从点a直接跳到点b只花费p,且|da - db| = k,即点a和点b之间的深度差为k。问从点u到点v的最小花费是多少?

分析

可以理解为在深度差为k的两层点之间各自连了一条权值为p的边,如下图:
图一
假设这两层点之间的高度差为k,如果要直接在这两层点的任意两个点之间连一条边的话,这显然是不现实的。我们可以在这两层点之间增加两个点,如下图:
图二
然后添加边:
图三
其中蓝色的边权为0,橙色的边权为p,这样一来,每个点最多会添加四条边
图四

听知乎博主严格鸽说用vector会爆内存,所以我就直接用链式前向星了

解题一共分为以下几步:

  • 根据题目输入的n - 1条边建图
  • dfs求出每个点的深度
  • 对每个点都增加四条边(可能会少于四条边)
  • 利用堆优化版dijstra求点u到点v的最小距离

代码

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
typedef pair<ll,ll> PII;
const ll N = 4e6 + 7;
ll t,n,from,to,k,p,idx = 0;
ll h[N],d[N],maxdep;
ll dis[N];
bool vis[N];
//注意要用long long!!!我这里为了方便就全都用long long了
struct edge{
	ll to,ne,w;//这条边的指向,权值
}e[N];

inline ll read()
{
    char ch = getchar();
    ll x = 0,f = 1;
    while(ch < '0' || ch > '9'){
        if(ch == '-') f = -1;
        ch = getchar();
    }   
    while(ch >= '0' && ch <= '9'){
        x = x * 10 + ch - '0';
        ch = getchar();
    }
    return x * f;
}

void add(ll x,ll y,ll w)
{
	e[idx].to = y;
	e[idx].w = w;
	e[idx].ne = h[x];
	h[x] = idx ++;
}

void dfs(ll u,ll fa)
{
    d[u] = d[fa] + 1;
	maxdep = max(maxdep,d[u]);
	for(ll i = h[u];~i;i = e[i].ne){
		if(e[i].to == fa) continue;
		dfs(e[i].to,u);
	}
}


void dij(ll u)
{
	memset(dis,0x3f,sizeof dis);
	memset(vis,false,sizeof vis);
	priority_queue<PII,vector<PII>,greater<PII>> Q;
	dis[u] = 0;
	Q.push({0,u});
	while(Q.size()){
		PII top = Q.top();
		Q.pop();
		ll x = top.second;
		if(vis[x]) continue;
		vis[x] = true;
		for(ll i = h[x];~i;i = e[i].ne){
			ll j = e[i].to;
			if(dis[j] > dis[x] + e[i].w){
				dis[j] = dis[x] + e[i].w;
				Q.push({dis[j],j});
			}
		}
	}
}	

int main()
{
    t = read();
    while(t --){
        n = read();
		idx = 0, maxdep = 0,d[0] = -1,d[1] = 0;
		memset(h,-1,sizeof h);
		for(ll i = 1;i < n;i ++){
			ll x = read(),y = read(),w = read();
			add(x,y,w);//无向边
			add(y,x,w);
		}
		k = read(),p = read(),from = read(),to = read();
		dfs(1,0);
		//加点
		for(ll i = 1;i <= n;i ++){
			if(d[i] != 0){
				add(n + 2 * d[i] - 1, i, p);//图四中的边1
			}
			if(d[i] != maxdep){
				add(n + 2 * (d[i] + 1), i, p);//图四中的边3
			}
			if(d[i] + k <= maxdep){
				add(i, n + 2 * (d[i] + k) - 1, 0);//图四中的边4
			}
			if(d[i] - k >= 0){
				add(i, n + 2 * (d[i] - k + 1), 0);//图四中的边2
			}
		}
		dij(from);
		cout << dis[to] << '\n';
    }
    return 0;
}

如果有不理解的地方大家可以发在评论区一起讨论喔~

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小王超能吃

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值