Freda的传呼机

题目大意

给定一个n个点,m条边的图,每个边最多仅在一个环中
有q个询问,每次询问给出任意两点x和y,输出x和y的最小距离。

数据范围n,q<=10000,m<=12000,边权<=32768。

每个边最多仅在一个环中——仙人掌

为了方便,我们先对原图进行一次spfa,设di[1]=0,算出其他的di。
然后建一个新图,每个环中,除顶点(原深度最小的点)外,都向顶点连一条权值为0的边,记录环的总长,同时删除环中的原边。
新图一定是棵树,于是我们就可以倍增求lca。
对于每个询问x,y
我们记p为原x,q为原y,然后x和y都跳到它们的lca的儿子位置。
若x,y不在同一环内,答案显然为di[p]+di[q]-2*di[lca]。
否则,我们可以算出x和y的距离,再加上p到x的距离和q到y的距离即可。

代码

#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fod(i,a,b) for(int i=a;i>=b;i--)
#define ll long long 
using namespace std;
const int maxn=6*10000+5;
int k[maxn],next[maxn],g[maxn],c[maxn],w[maxn],dfn[maxn];
int h[maxn],len[maxn],f[maxn][15],d[maxn],qu[maxn],be[maxn];
ll di[maxn],s[maxn];
bool bz[maxn],bk[maxn];
int i,j,num,cnt,n,m,q,t;
void read(int &n){
    char ch=getchar();
    while (ch<'0'||ch>'9') ch=getchar();
    n=0;while (ch>='0'&&ch<='9') n=n*10+ch-'0',ch=getchar();
}
void add(int x,int y,int t,int z){
    next[++num]=k[x],k[x]=num;
    g[num]=y,c[num]=t,w[num]=num+z;
}
void make(int x,int y,int l){
    bz[l]=bz[w[l]]=1;i=y;len[++cnt]=c[l];
    while (i!=x){
        h[i]=cnt;bz[be[i]]=bz[w[be[i]]]=1;add(x,i,0,0);
        len[cnt]+=c[be[i]],i=g[w[be[i]]];
    }
}
void dfs(int x){
    dfn[x]=++t;int i=k[x];
    while (i>0){
        if (!bz[i]){
            bz[w[i]]=1;
            if (dfn[g[i]]==0) be[g[i]]=i,s[g[i]]=s[x]+c[i],dfs(g[i]);
            if (dfn[g[i]]<dfn[x]) make(g[i],x,i);
        }i=next[i];
    }
}
void df(int x){
    int i=k[x];
    while (i>0){
        if (!bz[i]){
            f[g[i]][0]=x,d[g[i]]=d[x]+1,df(g[i]);
        }i=next[i];
    }
}
ll lca(int x,int y){
    if(d[x]<d[y]) swap(x,y);  
    int i,p=x,q=y;  
    fod(i,14,0) if(d[f[x][i]]>=d[y]) x=f[x][i];
    if(x==y) return di[p]-di[q];  
    fod(i,14,0)  if(f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i];  
    if(h[x]&&h[x]==h[y])  
    {  
        ll i=abs(s[x]-s[y]);
        return di[p]-di[x]+di[q]-di[y]+min(i,len[h[x]]-i);  
    }  
    else return di[p]+di[q]-2*di[f[x][0]];  
}
void spfa(){
    memset(di,63,sizeof(di)); 
    di[1]=0;qu[1]=bk[1]=1;
    int j=1,i=0;
    while (i<j){  
        int x=qu[++i];bk[x]=0;
        int l=k[x];
        while (l>0){
            int y=g[l];
            if (di[y]>di[x]+c[l]){
                di[y]=di[x]+c[l];
                if (bk[y]==0) qu[++j]=y,bk[y]=1;
            }
            l=next[l];
        }
    } 
}
int main(){
    read(n);read(m);read(q);
    fo(i,1,m){
        int x,y,t;read(x);read(y);read(t);
        add(x,y,t,1);add(y,x,t,-1);
    }spfa();dfs(1);d[1]=1;df(1);
    fo(j,1,14)fo(i,1,n) f[i][j]=f[f[i][j-1]][j-1];
    fo(i,1,q){
        int x,y;read(x);read(y);
        printf("%lld\n",lca(x,y));
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值