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;
}
View Code
没有人不辛苦,只有人不喊疼
原文地址:https://www.cnblogs.com/ctyakwf/p/14731099.html