虐场

问题描述

补觉完毕,查尔明开启了虐场模式,疯狂地屠掉前面那么多道逗逼题之后杀向了最后一
题。这一题是这样的:
给定一张有 n 个点 m 条边的无向图,每条边有边权,而且保证这张图是联通的。
现在有 q 次询问,每次询问会给出两个点 x,y,求 x 到 y 的最短路的长度。
不用多说,这也是一道逗逼题。查尔明分分钟就写好了总复杂度为 O(1)的标算,但是
他很担心自己是否手抖了,所以想请你写个程序来帮助对拍。当然对拍的程序也不能太慢,
因为查尔明非常喜欢用极限数据来跑对拍,这样才能体现出自己程序时间复杂度的优越性。
请你务必帮助查尔明完成对拍程序,这可是查尔明 AK 的最后一步!

输入格式

输入文件 allkill.in 第一行包含三个正整数 n,m,q,表示点数、边数和询问数。
接下来 m 行,每行包含三个正整数 u,v,w,表示 u 与 v 有一条边权为 w 的无向边。
接下来 q 行,每行包含两个正整数 x 和 y,表示一组询问。

输出格式

输出文件 allkill.out 包含 q 行,每行包含一个整数,依次回答每个询问。

这里写图片描述

题解

  • 其实这道题不是很难,看数据的约定就知道要分类讨论
  • 对于m=n-1 的情况分为两种,一种是纯粹的树,那么就是十分朴素的LCA,而对于退化的一条链来说,只需要线性的前缀和就好了(建树会爆栈),这里通过每个节点的度小于等于2来判断
  • 而对于m=n情况,就需要拿掉多余的边,然后分别进行LCA取最小值就好了
            ans=minn(get_sum(a,b),get_sum(a,ano.from)+get_sum(b,ano.to)+ano.v);
            ans=minn(get_sum(b,ano.from)+get_sum(a,ano.to)+ano.v,ans);//ano是多余的一条边

附上st做法的LCA,倍增算法在最后两个点会超时(查询时间复杂度太高了qwq)

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <vector>
#define INF 0xfffffff
using namespace std;
void fff(){
    freopen("allkill.in","r",stdin);
    freopen("allkill.out","w",stdout);
}
struct edge{
    int from,to,v;
    edge(int from,int to,int v):from(from),to(to),v(v){}
    edge(){
    }
};
edge ano;
const int MAXN=100010;
int num[MAXN];
vector < edge > Edge;
vector < int > G[MAXN];
int n,mm,q;
bool visited[MAXN];
int ver[2*MAXN],R[2*MAXN],first[MAXN];
int tot=0;
void dfs(int u,int deepth){
    visited[u]=true;
    ver[++tot]=u;
    first[u]=tot;R[tot]=deepth;
    int size=G[u].size();
    for (int i=0;i<size;i++){
        int v=Edge[G[u][i]].to;
        if(!visited[v]){
            num[v]=num[u]+Edge[G[u][i]].v;
            dfs(v,deepth+1);
            ver[++tot]=u;
            R[tot]=deepth;
        }
    }
}
int m[2*MAXN][22];
int min(int x,int y){
    return (R[x]>R[y])? y:x;
}
int minn(int x,int y){
    return (x>y)? y:x;
}
void st(){
    for (int i=1;i<=tot;i++){
        m[i][0]=i;
    }
    for (int j=1;(1<<j)<=tot;j++){
        for (int i=1;i+(1<<j)-1<=tot;i++){
            m[i][j]=min(m[i][j-1],m[i+(1<<(j-1))][j-1]);
        }
    }
}
int find(int i,int j){
    if(i>j){
        int temp=i;
        i=j;
        j=temp;
    }
    int k=log(j-i+1)/log(2);
    return min(m[i][k],m[j-(1<<k)+1][k]);
}
void init(){
    memset(ver,0,sizeof(ver));
    memset(R,0,sizeof(R));
    memset(first,0,sizeof(first));
    memset(visited,false,sizeof(visited));
    memset(m,0,sizeof(m));
}
int get_sum(int a,int b){
    int fa=ver[find(first[a],first[b])];
    return num[a]+num[b]-num[fa]*2;
}
int abs(int a){
    if(a<0) return -a;
    return a;
}
int main(){
    fff();
    bool falg=false;
    memset(visited,false,sizeof(visited));
    scanf("%d%d%d",&n,&mm,&q);
    for (int i=1;i<=mm;i++){
        int a,b,v;
        scanf("%d%d%d",&a,&b,&v);
        if(visited[a]&&visited[b]){
            ano=(edge){a,b,v};
            continue;
        }
        Edge.push_back((edge){a,b,v});
        G[a].push_back(Edge.size()-1);
        Edge.push_back((edge){b,a,v});
        G[b].push_back(Edge.size()-1);
        if(G[a].size()>2) falg=true;
        visited[a]=true;
        visited[b]=true;
    }
    init();
    num[1]=0;
    if(falg){
        dfs(1,1);
        st();
    }else{
        int u=1;
        visited[1]=true;
        while (n>1){
            int v,w;
            for (int i=0;i<G[u].size();i++){
                int temp=Edge[G[u][i]].to;
                if(!visited[temp]){
                    v=temp;
                    w=Edge[G[u][i]].v;
                    visited[temp]=true;
                }
            } 
            num[v]=num[u]+w;
            u=v;
            n--;
        }
    }
    for (int i=1;i<=q;i++){
        int a,b;
        int ans=INF;
        scanf("%d%d",&a,&b);
        if(n==mm){
            ans=minn(get_sum(a,b),get_sum(a,ano.from)+get_sum(b,ano.to)+ano.v);
            ans=minn(get_sum(b,ano.from)+get_sum(a,ano.to)+ano.v,ans);
        }else{
            if(falg){
                ans=get_sum(a,b);
            }else{
                ans=abs(num[a]-num[b]);
            }
        }
        printf("%d\n",ans);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值