【洛谷P4949】最短距离【树剖】【基环树】
题目大意:
题目链接:https://www.luogu.org/problem/P4949
给出一个个点条边的无向连通图。
你需要支持两种操作:
- 修改 第条边的长度为
- 查询 点到点的最短距离
共有次操作。
思路:
第一道没有看题解写出来的黑题祭。果然还是
这道题给出的图是一棵基环树,先考虑如果是一棵树应该如何处理。
显然一棵树的时候就是树链剖分的一道裸题。线段树单点修改和查询区间和即可。
那么在这棵树上加上一条边,原来的答案可能会有以下改变:
- 依然是没有加边前的答案
而我们对于一条路径,只需要求出的,然后路径长度就分成。
所以先找到基环树上的环,在环上随便删除一条边,然后每次询问就分别求出的长度,然后在上述三条式子中取最小值即可。
对了最开始肯定套路性的把边权转换为点权再做树剖。
时间复杂度,常数巨大,因为每次要求5组点对的距离,而每组点对又要拆分成两条路径并求一个。。。
肯定没有黑题难度啊。
代码:
#include <queue>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=100010;
int head[N],son[N],fa[N],size[N],dep[N],top[N],id[N],rk[N],in[N],U[N],V[N],Dis[N],val[N];
int n,m,tot,flag,opt,x,y,ans1,ans2,ans3,LCA;
queue<int> q;
struct edge
{
int next,to,dis;
}e[N*2];
void add(int from,int to,int dis)
{
e[++tot].to=to;
e[tot].dis=dis;
e[tot].next=head[from];
head[from]=tot;
}
struct Treenode
{
int l,r,sum;
};
struct Tree
{
Treenode tree[N*4];
int pushup(int x)
{
tree[x].sum=tree[x*2].sum+tree[x*2+1].sum;
}
void build(int x,int l,int r)
{
tree[x].l=l; tree[x].r=r;
if (l==r)
{
tree[x].sum=val[rk[l]];
return;
}
int mid=(l+r)>>1;
build(x*2,l,mid); build(x*2+1,mid+1,r);
pushup(x);
}
void update(int x,int k,int v)
{
if (tree[x].l==k && tree[x].r==k)
{
tree[x].sum=v;
return;
}
int mid=(tree[x].l+tree[x].r)>>1;
if (k<=mid) update(x*2,k,v);
else update(x*2+1,k,v);
pushup(x);
}
int ask(int x,int l,int r)
{
if (tree[x].l==l && tree[x].r==r)
return tree[x].sum;
int mid=(tree[x].l+tree[x].r)>>1;
if (r<=mid) return ask(x*2,l,r);
if (l>mid) return ask(x*2+1,l,r);
return ask(x*2,l,mid)+ask(x*2+1,mid+1,r);
}
}Tree;
void dfs1(int x,int f)
{
dep[x]=dep[f]+1; size[x]=1; fa[x]=f;
for (int i=head[x];~i;i=e[i].next)
{
int v=e[i].to;
if (v!=f)
{
dfs1(v,x);
size[x]+=size[v];
if (size[v]>size[son[x]]) son[x]=v;
}
}
}
void dfs2(int x,int tp)
{
top[x]=tp; id[x]=++tot; rk[tot]=x;
for (int i=head[x];~i;i=e[i].next)
if (e[i].to==son[x]) dfs2(e[i].to,tp);
for (int i=head[x];~i;i=e[i].next)
{
int v=e[i].to;
if (v!=son[x] && v!=fa[x]) dfs2(v,v);
}
}
int lca(int x,int y)
{
while (top[x]!=top[y])
{
if (dep[top[x]]<dep[top[y]]) swap(x,y);
x=fa[top[x]];
}
if (dep[x]<dep[y]) return x;
else return y;
}
int ask(int x,int y)
{
int ans=0;
while (top[x]!=top[y])
{
if (dep[top[x]]<dep[top[y]]) swap(x,y);
ans+=Tree.ask(1,id[top[x]],id[x]);
x=fa[top[x]];
}
if (dep[x]>dep[y]) swap(x,y);
return ans+Tree.ask(1,id[x],id[y]);
}
int ask_(int x,int y) //拆分成两条路径
{
LCA=lca(x,y);
return ask(x,LCA)+ask(y,LCA)-Tree.ask(1,id[LCA],id[LCA])*2;
}
void topsort() //基环树拓扑排序求环
{
for (int i=1;i<=n;i++)
if (in[i]==1) q.push(i);
while (q.size())
{
int u=q.front();
q.pop();
for (int i=head[u];~i;i=e[i].next)
{
int v=e[i].to;
in[v]--;
if (in[v]==1) q.push(v);
}
}
}
int main()
{
memset(head,-1,sizeof(head));
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++)
{
scanf("%d%d%d",&U[i],&V[i],&Dis[i]);
add(U[i],V[i],Dis[i]); add(V[i],U[i],Dis[i]);
in[U[i]]++; in[V[i]]++;
}
topsort();
tot=0;
memset(head,-1,sizeof(head));
for (int i=1;i<=n;i++)
if (in[U[i]]>1 && in[V[i]]>1 && !flag)
flag=i; //记录环上的任意一条边
else
add(U[i],V[i],Dis[i]),add(V[i],U[i],Dis[i]);
tot=0;
dfs1(1,0); dfs2(1,1);
for (int i=1;i<=n;i++)
if (dep[U[i]]>dep[V[i]])
val[U[i]]=Dis[i];
else
val[V[i]]=Dis[i];
Tree.build(1,1,n);
while (m--)
{
scanf("%d%d%d",&opt,&x,&y);
if (opt==1)
{
if (x==flag) Dis[x]=y;
else if (dep[U[x]]>dep[V[x]]) Tree.update(1,id[U[x]],y);
else Tree.update(1,id[V[x]],y);
}
else
{
ans1=ask_(x,y);
ans2=ask_(x,U[flag])+ask_(y,V[flag])+Dis[flag];
ans3=ask_(x,V[flag])+ask_(y,U[flag])+Dis[flag];
printf("%d\n",min(ans1,min(ans2,ans3)));
}
}
return 0;
}