题意:给你一个含n条边的带权无向连通图,q次查询,每次查询两点间的最短距离。
思路:LCA+思维。
设a,b两点间的距离为f(a,b) 则f(a,b)=dis[a]+dis[b]-2*dis[lca(a,b)];
由于n条边,因此我们先任取一条边,设这条边为X,Y,权值为Z,设查询的点为x,y,则答案为
min(f(a,b),f(a,X)+f(b,X),f(a,Y)+f(b,Y),f(a,X)+f(b,Y)+Z,f(a,Y)+f(b,X)+Z);


#include<bits/stdc++.h> #define lson (i<<1) #define rson (i<<1|1)using namespace std; typedef long long ll; const int N =1e5+5;struct node {int u,v,next;int id,f;ll w; }edge[N*2];struct node1 {int l,r;ll w;ll lz; }tr[N<<2];int tot,head[N]; int n,q; int anc[N<<1][20];int dfn; int dfns[N*2]; int dep[N*2]; int pos[N]; int inde[N]; int L[N]; int R[N]; int clo;int to[N]; int vis[N]; ll ww[N];int uu,vv; ll cost; int huan; int idd;void init() {tot=0;memset(head,-1,sizeof(head));memset(vis,0,sizeof(vis));memset(inde,-1,sizeof(inde));memset(pos,-1,sizeof(pos));clo=0; huan=0; idd=0; dfn=0; /// dfn竟然没清零 MMP }void add(int u,int v,ll w,int id) {edge[++tot].u=u; edge[tot].v=v; edge[tot].id=id; edge[tot].w=w; edge[tot].f=0;edge[tot].next=head[u]; head[u]=tot; }void dfs1(int u,int fa) {if(vis[u]){uu=fa; vv=u; huan=1; return ;}vis[u]=1;for(int i=head[u];i!=-1;i=edge[i].next){int v=edge[i].v;if(v==fa) continue;dfs1(v,u);} }void dfs2(int u,int deep) /// dfs序 {//cout<<" u "<<u<<" deep "<<deep<<endl;dfns[dfn]=u; dep[dfn]=deep; pos[u]=dfn++;L[u]=++clo;inde[u]=L[u]; /// 记录u在线段树中的位置for(int i=head[u];i!=-1;i=edge[i].next){if(edge[i].f) continue; /// 如果是标记的边跳过int v=edge[i].v;if(pos[v]==-1){to[edge[i].id]=v; /// 表示这条边指向哪个点?dfs2(v,deep+1);dfns[dfn]=u; dep[dfn++]=deep;}}R[u]=clo; }void init_RMQ(int n) /// dfn {for(int i=1;i<=n;++i) anc[i][0]=i;for(int j=1;(1<<j)<=n;++j)for(int i=1;i+(1<<j)-1<=n;++i){if(dep[anc[i][j-1]]<dep[anc[i+(1<<(j-1))][j-1]]) anc[i][j]=anc[i][j-1];else anc[i][j]=anc[i+(1<<(j-1))][j-1];} }inline int RMQ(int L,int R) {int k=0;while(1<<(k+1)<=R-L+1) ++k;if(dep[anc[L][k]]<dep[anc[R-(1<<k)+1][k]]) return anc[L][k];return anc[R-(1<<k)+1][k]; }inline int LCA(int u,int v) {if(pos[u]>pos[v]) return dfns[RMQ(pos[v],pos[u])];return dfns[RMQ(pos[u],pos[v])]; }void push_up(int i) {tr[i].w=tr[lson].w+tr[rson].w; }void push_down(int i) {if(tr[i].lz){ /// 查询只有点查询,所以不必更新区间点的sumll &lz=tr[i].lz;tr[lson].lz+=lz; tr[rson].lz+=lz;tr[lson].w+=lz; tr[rson].w+=lz;lz=0;} }void build(int i,int l,int r) {tr[i].l=l; tr[i].r=r; tr[i].w=0; tr[i].lz=0;if(l==r) return ;int mid=(l+r)>>1;build(lson,l,mid);build(rson,mid+1,r); }void update(int i,int l,int r,ll w) {if(tr[i].l==l&&tr[i].r==r){tr[i].lz+=w; tr[i].w+=w;return ;}push_down(i);int mid=(tr[i].l+tr[i].r)>>1;if(r<=mid) update(lson,l,r,w);else if(l>mid ) update(rson,l,r,w);else{update(lson,l,mid,w);update(rson,mid+1,r,w);}push_up(i); }ll query(int i,int aim) {if(tr[i].l==tr[i].r&&tr[i].l==aim){return tr[i].w;}push_down(i);int mid=(tr[i].l+tr[i].r)>>1;if(aim<=mid) return query(lson,aim);else return query(rson,aim); }ll getans(int u,int v) {int lca=LCA(u,v);ll sum1,sum2,sum3;sum1=query(1,L[u]); sum2=query(1,L[v]);sum3=query(1,L[lca]);return sum1+sum2-sum3*2; }int main() {int T;int u,v,op;ll w;scanf("%d",&T);while(T--){scanf("%d %d",&n,&q);init();for(int i=1;i<=n;i++){scanf("%d %d %lld",&u,&v,&w);add(u,v,w,i);add(v,u,w,i);ww[i]=w;}dfs1(1,-1);/// 第一遍dfs 先找到环中的任意一条边for(int i=1;i<=tot;i++){ /// 给边打上标记if((edge[i].u==uu&&edge[i].v==vv)||(edge[i].u==vv&&edge[i].v==uu)){edge[i].f=1;idd=edge[i].id;cost=edge[i].w;}}dfs2(1,0);/*cout<<"dfn "<<dfn<<endl;cout<<uu<<" *** "<<vv<<endl;for(int i=1;i<=n;i++){cout<<"l "<<L[i]<<" r "<<R[i]<<endl;}*/init_RMQ(dfn);build(1,1,n); /// 以dfs的遍历出的 L,R 建树 那么接下来就是一个区间更新,单点查询的问题了for(int i=1;i<=n;i++){if(i==idd) continue;u=to[i];update(1,L[u],R[u],ww[i]);}/*for(int i=1;i<=n;i++){ll tmp=query(1,L[i]);cout<<" dis "<<tmp<<endl;}*/while(q--){scanf("%d %d",&u,&v);ll ans=getans(u,v);ans=min(ans,getans(uu,u)+getans(vv,v)+cost); /// 经过标记的路的两个不同的方向。ans=min(ans,getans(uu,v)+getans(vv,u)+cost);printf("%lld\n",ans);}}return 0; }