题意:n点n条边的无向带权图,m次操作.
操作1:修改x-y的边权
操作2:询问x-y的最短路.
n,m<=1e5.1<=w[i]<=1e5
因为是n条边的联通图,也就是环套树的形式.
并查集找出环上任意一条边(u,v).
现在(x,y)的最短路可以分为:是否经过边(u,v).
不经过边(u,v) 则(x,y)的距离为树上两点的距离.
经过边(u,v) 注意有两种方案: x->u->v->y, x->v->u->y.
修改边权+树上两点间距离,树链剖分维护一下即可.
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e5+5,M=2e5+5;
int T,Q,n,tot,a[N],b[N],c[N];
int head[N],fa[N],par[N],sz[N],dep[N];
int tim,pos[N],bl[N],cost[N],son[N];
int U,V,W;
int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);}
struct edge{
int to,w,nxt;
}e[M];
struct node{
int l,r;
ll sum;
}t[N<<2];
void add_edge(int u,int v,int w){
e[++tot].to=v,e[tot].w=w;
e[tot].nxt=head[u],head[u]=tot;
}
void init(){
memset(head,-1,sizeof(head));
tim=tot=0;
scanf("%d%d",&n,&Q);
for(int i=1;i<=n;i++) fa[i]=i;
for(int i=1;i<=n;i++){
scanf("%d%d%d",&a[i],&b[i],&c[i]);
int x=find(a[i]),y=find(b[i]);
if(x!=y){
add_edge(a[i],b[i],c[i]);
add_edge(b[i],a[i],c[i]);
fa[x]=y;
}
else U=a[i],V=b[i],W=c[i];
}
}
void dfs(int u){
sz[u]=1;
for(int i=head[u];i!=-1;i=e[i].nxt){
int v=e[i].to;
if(v==par[u]) continue;
dep[v]=dep[u]+1,par[v]=u;
cost[v]=e[i].w;
dfs(v);
sz[u]+=sz[v];
}
}
void dfs(int x,int chain){
int k=0;
pos[x]=++tim;
bl[x]=chain;
for(int i=head[x];i!=-1;i=e[i].nxt){
int v=e[i].to;
if(dep[v]>dep[x]&&sz[v]>sz[k]) k=v;
}
if(k==0){
son[x]=x;
return;
}
son[x]=k;
dfs(k,chain);
for(int i=head[x];i!=-1;i=e[i].nxt)
if(dep[e[i].to]>dep[x]&&k!=e[i].to)
dfs(e[i].to,e[i].to);
}
void build(int o,int l,int r){
t[o].l=l,t[o].r=r;
if(l==r) return;
int mid=l+r>>1;
build(o<<1,l,mid);
build(o<<1|1,mid+1,r);
}
void push_up(int o){
t[o].sum=t[o<<1].sum+t[o<<1|1].sum;
}
void change(int o,int pos,int val){
int l=t[o].l,r=t[o].r,mid=l+r>>1;
if(l==r){
t[o].sum=val;
return;
}
if(pos<=mid) change(o<<1,pos,val);
else change(o<<1|1,pos,val);
push_up(o);
}
ll querysum(int o,int ql,int qr){
int l=t[o].l,r=t[o].r,mid=l+r>>1;
if(l>=ql&&r<=qr) return t[o].sum;
ll sum=0;
if(ql<=mid) sum+=querysum(o<<1,ql,qr);
if(qr>mid) sum+=querysum(o<<1|1,ql,qr);
return sum;
}
ll solvesum(int x,int y){
ll sum=0;
while(bl[x]!=bl[y]){
if(dep[bl[x]]<dep[bl[y]]) swap(x,y);
sum+=querysum(1,pos[bl[x]],pos[x]);
x=par[bl[x]];
}
if(pos[x]>pos[y]) swap(x,y);
sum+=querysum(1,pos[son[x]],pos[y]);
return sum;
}
void solve(){
build(1,1,n);
cost[1]=0;
for(int i=1;i<=n;i++) change(1,pos[i],cost[i]);
int op,x,y,w;
while(Q--){
scanf("%d%d",&op,&x);
if(op==0){
scanf("%d",&w);
int u=a[x],v=b[x];
int son=par[v]==u?v:u;
cost[son]=w;
change(1,pos[son],cost[son]);
}
else{
scanf("%d",&y);
ll v1=solvesum(x,y);
ll v2=solvesum(x,U)+W+solvesum(V,y);
ll v3=solvesum(x,V)+W+solvesum(U,y);
printf("%I64d\n",min(v1,min(v2,v3)));
}
}
}
int main(){
scanf("%d",&T);
while(T--){
init();
dfs(1);
dfs(1,1);
solve();
}
return 0;
}
复习:
树剖:任意一个节点到根的路径上不超过Log条轻边和重链.
因为轻儿子的大小<父节点的一半,往上走,子树大小至少变为2倍.重链是间断的,个数不会超过轻边+1.