旅行【图论】【堆】

本文深入探讨了一种解决特定无向图最短路径问题的算法,通过对树状结构的预处理和优化,利用小根堆进行路径查找,实现了高效计算。文章详细介绍了算法的实现过程,包括数据结构设计、核心函数定义及调用流程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

>Description
在这里插入图片描述


>Input
在这里插入图片描述

>Output
在这里插入图片描述
在这里插入图片描述


>Sample Input
5 6
1 2 3
1 3 4
2 4 2
2 5 3

>Sample Output
4


>解题思路
由题意得,此无向图可以看做成一棵树,如:
在这里插入图片描述
求某两点的最短路代价,通过推算得出(题目中的加加减减),a ~ b = a ~ 1(根节点) + 1 ~ b,并且第一个符号与最后一个符号肯定都为加
因为题目要求的路径的条数必须为奇数,所以符合条件的路径必须是从偶数层出发到达奇数层的(也可以反过来,不过都一样),所以我们可以对1~任何点的代价做一个预处理(注意处理到任何点的最后一个符号为+,这里可以统一处理,最后不符合的取相反数),把它们分为偶数层出发disa和奇数层出发disb两个数组,分别从小到大排个序,disa+disb维护一个小根堆(这里用到两个指针分别指向两个数组),第k个弹出的就是答案。


>代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define ll long long
using namespace std;

struct ooo
{
	ll to, next, c;
} a[200005];
struct ooo2
{
	ll c, h; //堆数组,c记录数据,h记录这个节点指向的disa的位置
} d[500005];
ll n, k, x, y, z, t, dep[100005], h[100005], f[100005];
ll disa[100005], disb[100005], u[100005];

void dfs (ll s, ll fath, ll p) //s当前节点,fath为s的父亲,p记录符号
{
	dep[s] = dep[fath] + 1; //计算深度
	for (ll i = h[s]; i; i = a[i].next)
	  if (a[i].to != fath)
	  {
	  	f[a[i].to] = f[s] + p * a[i].c; //dp计算1~i的代价
	  	dfs (a[i].to, s, -p); //要变符号
	  }
}

void swapp (ll aa, ll bb)
{
	ll tt;
	tt = d[aa].c, d[aa].c = d[bb].c, d[bb].c = tt;
	tt = d[aa].h, d[aa].h = d[bb].h, d[bb].h = tt;
} //交换

void up (ll s)
{
	while (s / 2 != 0 && d[s / 2].c > d[s].c)
	{
		swapp (s / 2, s);
		s = s / 2;
	}
}

void down (ll s)
{
	while ((d[s * 2].c != d[0].c && d[s].c > d[s * 2].c) 
	     || (d[s * 2 + 1].c != d[0].c && d[s].c > d[s * 2 + 1].c))
	{
		ll l = s * 2;
		if (d[s * 2 + 1].c < d[s * 2].c) l++;
		swapp (s, l);
		s = l;
	}
} 

int main()
{
	scanf ("%I64d%I64d", &n, &k);
	for (ll i = 1; i < n; i++)
	{
		scanf ("%I64d%I64d%I64d", &x, &y, &z);
		a[++t].to = y;  a[t].next = h[x];  a[t].c = z;  h[x] = t;
		a[++t].to = x;  a[t].next = h[y];  a[t].c = z;  h[y] = t;
	}
	dep[0] = -1; //我用的是1为根节点,深度为0
	dfs (1, 0, 1);
	disa[++disa[0]] = 0; //注意a也可以为树的根节点,此时距离为0
	for (ll i = 2; i <= n; i++)
	  if (dep[i] % 2 == 0) disa[++disa[0]] = -f[i]; //处理:使偶数层最后的符号也为+
	  else disb[++disb[0]] = f[i];
	sort (disa + 1, disa + 1 + disa[0]);
	sort (disb + 1, disb + 1 + disb[0]); //排序
	t = disa[0]; //t记录小根堆的大小
	
	for (ll i = 0; i < 500005; i++) d[i].c = 9223372036854775807; //longlong上线
	for (ll i = 1; i <= t; i++) u[i] = 1, d[i].c = disa[i] + disb[1], d[i].h = i; //加入堆
	                            //u[i]记录disa[i]现在指向的disb的位置
	for (ll i = 2; i <= t; i++) up(i); //建堆
	for (ll i = 1; i <= k; i++)
	{
		if (i == k)
		{
			if (t <= 0) printf ("Stupid Mike"); //不知道为什么在学校oj上交的这个点是错的但是好像没有什么毛病TT
		    else printf ("%I64d", d[1].c);
		}
		if (u[d[1].h] + 1 <= disb[0]) d[1].c = disa[d[1].h] + disb[++u[d[1].h]]; //如果还有匹配的disb
		else 
		{
			swapp (1, t);
			d[t].c = d[0].c, d[t].h = 0;
			t--; //如果没有了的话就去掉一个点
		}
		down(1);
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值