2021浙江省大学生程序设计竞赛D题 Shortest Path Query(最短路+思维)
这道题我们肯定敏锐的观察到题目的一个特殊性质
只有一个数是另一个数的二进制前缀才能连边,这个性质肯定要用到,等会我们来思考。
首先是暴力算法,假设要知道两两点的最短路,最暴力的想法就是求floyd,这个太夸张了,我们显然可以放弃掉。
之后就是对于每个点求迪杰斯特拉,这个算法做一次复杂度过关,但是做多次在普通的图上显然是超时算法。
这下就用到了题目给的性质。对于一些点,不管他们是什么,如果他们的前缀相同,那么他们就有一些共同的父亲节点,假设可以到达的话。
我们没有办法存在每个点到任意点的距离,但是我们可以存他们到父亲点的距离,因为前缀没几个。对于这个特殊的图,两点之间可达,一定是有前缀相同
因此我们可以枚举这个前缀,按照lca的思路,分别是他们到前缀的路径和的最小值。
这样只要对每个点跑迪杰斯特拉求这些值就行了。
对每个点跑迪杰斯特拉的正确性在于,我们每次只更新的小的点往大的点的这些路径。对于每个点,他的扩展有01两种情况,因此均摊下来,对于每个以他为根的子图当中的点差不多是log级别,可以接受。
但是注意每次不能把所有信息的清空,需要遇到一个清空一个。
#include<bits/stdc++.h> using namespace std; typedef long long ll; typedef pair<ll,int> pll; const int N=2e5+10; const int M=2e6+10; int n,m; int h[N],ne[M],e[M],w[M],idx; ll dis[N][20]; int st[N],vis[N]; ll d[N]; void add(int a,int b,int c){ e[idx]=b,ne[idx]=h[a],w[idx]=c,h[a]=idx++; } int cal(int u,int v){ int cnt=0; while(v>u){ v>>=1; cnt++; } return cnt; } void dij(int rt){ priority_queue<pll,vector<pll>,greater<pll>> q; q.push({0,rt}); d[rt]=0; vis[rt]=rt; while(q.size()){ auto t=q.top(); q.pop(); if(st[t.second]==rt) continue; st[t.second]=rt; dis[t.second][cal(rt,t.second)]=d[t.second]; for(int i=h[t.second];i!=-1;i=ne[i]){ int j=e[i]; if(j<rt) continue; if(vis[j]!=rt){ vis[j]=rt; d[j]=1e16; } if(d[j]>d[t.second]+w[i]){ d[j]=d[t.second]+w[i]; q.push({d[j],j}); } } } } int lca(int a,int b){ while(a!=b){ if(a>b) a>>=1; else b>>=1; } return min(a,b); } int getsz(int u){ int cnt=0; while(u){ cnt++; u>>=1; } return cnt; } int main(){ ios::sync_with_stdio(false); memset(h,-1,sizeof h); cin>>n>>m; int i,j; for(i=1;i<=n;i++){ for(j=0;j<=19;j++){ dis[i][j]=1e16; } } for(i=1;i<=n;i++){ dis[i][0]=0; } for(i=1;i<=m;i++){ int a,b,c; cin>>a>>b>>c; add(a,b,c); add(b,a,c); } for(i=1;i<=n;i++){ dij(i); } int q; cin>>q; while(q--){ int a,b; cin>>a>>b; int p=lca(a,b); int l1=getsz(a)-getsz(p); int l2=getsz(b)-getsz(p); ll ans=1e16; while(p){ ans=min(ans,dis[a][l1]+dis[b][l2]); l1++,l2++; p>>=1; } if(ans==1e16) cout<<-1<<endl; else cout<<ans<<endl; } return 0; }
没有人不辛苦,只有人不喊疼