HDU6393(LCA + RMQ + 树状数组) n边图,两点最短距离 , 修改边权
题意:
一个n个点,n条边的图,2中操作,1是将某条边的权值更改,2是询问两点的最短距离。
题解:
由于n个点,n条边,所以是树加一个环,将环上的边随意取出一条,就是1颗树,以取出的边的一个端点为根,建立有根树。虚线就是取出的边。红色为环上的边。
对于更改边的权值的操作,用dfs序+区间修改点查询的树状树组维护。
对于询问最短路的操作,用LCA分类解决。假设询问的两点是x、y,LCA是 z。
若z不是环上的点,那么最短路就是:x到根的距离+y到根的距离-z到根的距离*2;
若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){ /// 查询只有点查询,所以不必更新区间点的sum ll &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",&op); if(op==0){ scanf("%d %lld",&u,&w); if(u==idd){ /// 如果是标记的环中的边,那么就没必要更新线段树 ww[u]=w; } else{ int tmp=to[u]; //cout<<"tmp "<<tmp<<" L "<<L[tmp]<<" R "<<R[tmp]<<" w "<<ww[u]<<endl; update(1,L[tmp],R[tmp],-ww[u]); ww[u]=w; update(1,L[tmp],R[tmp],ww[u]); } } else{ 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; }