最短路
其实,算是最擅长的算法,可能刷的多
这里主要记录那些刷到有意思的题目
比特跳跃(杭电多校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;
}