P4768 [NOI2018]归程(Kruskal重构树,倍增,单源最短路径)

求求你不要再封装了!struct封装锅很多的! \color{#ee0000} {\LARGE\text{求求你不要再封装了!struct封装锅很多的!}} 求求你不要再封装了!struct封装锅很多的!

发现Kruskal重构树和倍增通常一起使用呢!

P4768 [NOI2018]归程

题目描述

自己看题面吧,不可描述.

题目分析

从大体思路入手, 我们可以把从v到1的路径分成两部分, 一半全开车,一半全走路.

也就是说要枚举n个节点作为断点(假设当前断点为u), 这个断点是可行解与最优解当且仅当

存在一条从v到u的路径可以全部开车
且从u到1全部走路的最短路是满足上一条件中最短的

那么要怎么求出从v出发可以开车到的点呢, 显然从v出发开车可以到的点, 一定满足路径上所有边海拔都高于水位,

这里已经很明显可以用Kruskal重构树求解了.

K r u s k a l Kruskal Kruskal 重构树

“查询从某个点出发经过边权不超过val的边所能到达的节点”,诸如此类的题目,第一反应应该就是 K r u s k a l Kruskal Kruskal重构树.

由于本题中有水面高度的限制,不妨以每条边的海拔高度为关键字对边进行由大到小排序,这样的重构树就是一棵小根堆,到时候找大于水面高度的最小值就可以了.

他的子树的全部节点都可以由v开车到达. (由重构树性质得证)

倍增

同样的套路,找水面高度的最小值,用倍增显然快.


以上我们就处理出了大于水面高度的所有点. 那么要到点1的距离最小,是不是就是在这些点里面选呢?所以要处理出点1到大于水面高度的每个点的最短路径,然后再从这些点里面选择到点1距离最小的点.

单源最短路径

显然每个点到1的最短路径是一定的,可以预处理.

不要用 s p f a spfa spfa! “它死了”,就在这道题!


我们发现,由重构树的性质,能相互通达的点一定有共同的LCA,所以LCA可以维护到点1的路径的最小值,只需在回溯时更新父亲的最小值即可.

程序实现

最后捋一捋思路:我们首先要求出每个点到1号点的最小花费,这个直接 d i j s t r a dijstra dijstra最短路预处理. 然后是要建出重构树,再然后维护以每个点作为根节点时子树中距离1号点的最小花费,这个建完树后一个简单的dfs搞定. 最后是如何找到点u,这时我们要让一个重要的算法登场:倍增算法. 直接加上点权>p的限制在树上倍增即可.

听起来很顺?你以为这道题那么简单?不!来看看我这两天碰上的玄学错误吧

#include<bits/stdc++.h>
#define maxn 2000010
using namespace std;
int head[maxn],tot;//用于求最短路 
int tree_head[maxn],sum;//用于重构树 
int n,m;
int f[maxn],fa[maxn][25],dif[maxn];
struct Edge{
   
	int u,v,len,height,next;
	void add(int x,int y,int l,int h){
   u=x,v=y,len=l,height=h;}
	void add_edge(int x,int y,int l,int h){
   
		u=x,v=y,len=l,height=h;
		next=head[u];
		head[u]=tot;
	}
//	void _add(int x,int y){
   
//		v=y;
//		next=tree_head[u];
//		tree_head[u]=sum;
//		printf("%d\n",tree_head[u]);
//		printf("%d to %d",x,y);
//		printf("all right\n");
//	}
	bool operator <(Edge path)const{
   
		return path.height <height;
	}
};
Edge e[maxn];//用于求最短路 
Edge E[maxn];//用于构建Kruskal树 
Edge ed[maxn];//用于存储输入数据 
struct node{
   
	int pos,dis;
	bool operator <(node nd)const {
   
		return dis>nd.dis ;
	}
};
priority_queue<node >q;
bool vis[maxn];
int dis[maxn];
void dijkstra(){
   
	dis[1]=0;
	q.push((node){
   1,0});
	while(!q.empty()){
   
		node now=q.top();
		q.pop();
		int u=now.pos ;
		if(vis[u])continue;
		vis[u]=true;
		for(int i=head[u];i;i=e<
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值