图论 最短路

最短路

其实,算是最擅长的算法,可能刷的多

这里主要记录那些刷到有意思的题目

比特跳跃(杭电多校2024)

Problem Description

比特国由 n 个城市组成,编号为 1,2,⋯,n。

有 m 条双向道路连接这些城市,第 i 条连通城市 ui 和城市 vi,通过这条道路需要花费 ti 的时间。

此外,比特国的人们还可以使用“比特跳跃“来通行于任意两个城市之间。

从城市 x 通过比特跳跃移动到城市 y 需要花费 k×(x|y) 的时间,其中 | 表示按位或。比特跳跃可以使用任意多次。

现在请你计算出,从 1 号城市移动到每个城市所需的最短时间。

Input

第一行一个整数 t (1≤t≤15),代表数据组数。

对于每组数据:

第一行三个整数 n,m,k (2≤n≤105,0≤m≤105,0≤k≤106),代表城市个数,道路条数,比特跳跃的系数。

接下来 m 行,每行三个整数 ui,vi,ti (1≤ui,vi≤n,0≤ti≤109) ,代表一条道路的信息。

保证所有测试数据的 ∑n≤ 1 0 6 10^6 106,∑m≤$10 ^ 6 $ 。

如果没有比特跳跃的话,就是单源最短路板子题,没学过的可以去洛谷学一下
这里,难点在于比特跳跃:从城市 x 通过比特跳跃移动到城市 y 需要花费 k×(x|y) 的时间
我们要观察它有什么性质:

如果一个城市x是由若干次跳跃来的,假设由某个:k->x:如果k不是x的二进制子集的话,$k|x > k|1 ,而且 ,而且 ,而且dis[1]=0<dis[x]$,所以一定从1跳跃

  • 性质:一个城市x如果由比特跳跃来的话,来源只有两个:
    • 1号城市来(即源点
    • k号城市(k为x的二进制子集

所以我们先源点跑一边单源最短路,然后每个城市枚举子集看一下有没有更优的比特跳跃,最后因为有的城市是由比特跳跃之后的一些城市走来的,所以跑一个多源最短路

trick:枚举子集(在状压DP中有时候会出现的Trick)
for (int s1=s-1;s1;s1=(s1-1)&s)\实现了枚举s的真的二进制子集
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+10;
const int M=2e5+10;
int T,n,m,k,u,v,z,cnt,dis[N],hd[N],now;
bool vis[N];
struct st{
	int to,next,dis;
}edge[M];
void add(int u,int v,int z){
	edge[++cnt].to=v;
	edge[cnt].next=hd[u];
	edge[cnt].dis=z;
	hd[u]=cnt;
	return ;
}
priority_queue < pair<int,int> ,vector< pair<int,int> >, greater < pair<int,int> > > q;
pair<int,int> x;
void Dij1(){
	q.push(make_pair(0ll,1ll));
	while (!q.empty()){
		x=q.top();
		q.pop();
		now=x.second;
		if (vis[now])continue;
		vis[now]=true;
		for (int i=hd[now];i;i=edge[i].next){
			if (x.first+edge[i].dis<dis[edge[i].to]){
				dis[edge[i].to]=x.first+edge[i].dis;
				q.push(make_pair(dis[edge[i].to],edge[i].to));
			}
		}
	}
	return ;
}
void Dij2(){
	for (int i=1;i<=n;i++){
		vis[i]=false;
		q.push(make_pair(dis[i],i));
	}
	while (!q.empty()){
		x=q.top();
		q.pop();
		now=x.second;
		if (vis[now])continue;
		vis[now]=true;
		for (int i=hd[now];i;i=edge[i].next){
			if (x.first+edge[i].dis<dis[edge[i].to]){
				dis[edge[i].to]=x.first+edge[i].dis;
				q.push(make_pair(dis[edge[i].to],edge[i].to));
			}
		}
	}
	return ;
}
signed main(){
	scanf("%lld",&T);
	while (T--){
		scanf("%lld%lld%lld",&n,&m,&k);
		dis[1]=0;
		hd[1]=0;
		cnt=0;
		vis[1]=false;
		for (int i=2;i<=n;i++){
			dis[i]=1e18;
			hd[i]=0;
			vis[i]=false;
		}
		for (int i=1;i<=m;i++){
			scanf("%lld%lld%lld",&u,&v,&z);
			add(u,v,z);
			add(v,u,z);
		}
		Dij1();//一开始只可以从1号城市出发
		for (int i=2;i<=n;i++){
			for (int j=i-1;j;j=(j-1)&i){
				dis[i]=min(dis[i],dis[j]+k*(i|j));
			}
			dis[i]=min(dis[i],k*(1ll|i));
		}
		Dij2();//比特跳跃之后每个城市都可以作为起点
		printf("%lld",dis[2]);
		for (int i=3;i<=n;i++)printf(" %lld",dis[i]);
		printf("\n");
	}
	return 0;
} 
引用\[1\]提供了使用Python的networkx库绘制网络图和计算最短加权路径的示例代码。该代码使用了一个包含顶点和边的列表,并使用add_nodes_from和add_weighted_edges_from方法将它们添加到图中。然后,使用nx.shortest_path_length方法计算了从顶点v1到顶点v11的最短加权路径长度为13。\[1\] 引用\[2\]提供了一个计算最短路径的Python程序示例。该程序使用了numpy和networkx库。首先,定义了一个包含顶点和边的列表,并使用add_nodes_from和add_weighted_edges_from方法将它们添加到图中。然后,使用nx.shortest_path_length方法计算了最短路径长度,并将结果存储在一个字典中。接下来,使用numpy创建了一个6x6的零矩阵,并使用两个嵌套的for循环将最短路径长度填充到矩阵中。最后,使用矩阵乘法计算了运力,并找到了最小运力和对应的位置。\[2\] 引用\[3\]提供了关于Dijkstra算法的一些背景信息。Dijkstra算法是一种寻找最短路径的算法,适用于所有权重大于等于0的情况。它可以用于解决从一个起始点到任意一个点的最短路径问题。\[3\] 综上所述,如果你想在Python中计算图论中的最短路径,可以使用networkx库和Dijkstra算法。你可以根据引用\[1\]和引用\[2\]中的示例代码进行操作。 #### 引用[.reference_title] - *1* *3* [运筹学——图论与最短距离(Python实现)](https://blog.csdn.net/weixin_46039719/article/details/122521276)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [数学建模:图论模型 — 最短路模型示例 (Python 求解)](https://blog.csdn.net/qq_55851911/article/details/124776487)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值