题目描述:
给你一个图,一共有 N 个点,2 * N - 2 条有向边。
边目录按两部分给出
(1)开始的 N-1 条边描述了一颗以 1 号点为根的生成树,即每个点都可以由 1 号点到达。
(2)接下来的 N-1 条边,一定是从 i 到 1(2 ≤ i ≤ N)的有向边,保证每个点都能到 1。
有 q 次询问:
1 x w :表示将第 x 条边的边权修改为 w
2 u v :询问 u 到 v 的最短距离
输入格式:
第一行是 2 个整数 N , Q,表示一共 N 个点 Q 次询问
接下来是 N - 1 行,每行 3 个整数 U , V , W,表示了前 N - 1 条边,U 到 V 的有向边
接下来 N - 1 行,每行 3 个整数 U , V , W,描述了每个点到 1 号点的边,V == 1
接下来是 Q 行,表示 Q 次修改与询问
输出格式:
若干行,每行回答一次询问
样例数据:
输入
5 9
1 3 1
3 2 2
1 4 3
3 5 4
5 1 5
3 1 6
2 1 7
4 1 8
2 1 1
2 1 3
2 3 5
2 5 2
1 1 100
2 1 3
1 8 30
2 4 2
2 2 4
输出
0
1
4
8
100
132
10
备注:
【数据规模】
20%数据,没有修改
30%数据,2 ≤ N , Q ≤ 1000 (其中有 10% 数据没有修改)
100%数据,2 ≤ N , Q ≤ 100 000, 1 ≤ 边权 ≤ 1000,000
分析
膜拜 ldx 大佬今天AK全场
我猜聪明的你应该又知道我学倍增法求 lca 的真正目的了吧,没错就是为这道题做铺垫!!!!
先让我乱bibi一下:%%%%%%%%%%感谢gsj大佬%%%%%%%%%%,让我错误百出的程序AC了!!!!
好了,我们来认真分析一下这道题
-->参考<--
我们用 dis(x) 表示从 1 经过树枝边到达 x 的距离,re(x) 表示从 x 到 1 的距离
首先来说询问,对于一组询问 u,v,有以下两种情况:
(1)u 是 v 的祖先,那么 u 到 v 的最短距离就是 dis(v) - dis(u)
(2)如果 u 不是 v 的祖先,那么 u 就只能通过一条路径到达 1,然后又从 1 到达 v。从 1 到 v 的距离唯一,就是 dis(v),但是从 u 到 1 就可能有多条,可以直接从 u 回到 1,也可以走到 u 子树中的一个点,从那个点回到 1,我们就要在这些路径中找出最短的那一条(在考场上的时候我就是在这个地方分析出错,我只考虑了直接从 u 回到 1 ,没想到还可以走子树返回 1 )
我们考虑用 dfs 序来构建一个序列,这样 u 的子树一定会是连续的一段,我们用 st(x) 表示 x 的子树开始的位置(其实就是 x),用 end (x) 表示 x 的子树结束的位置,这些可以一遍 dfs 搞定
对于情况 2,我们要快速求出 st(u)~end(u) 之间 dis(x) + re(x) 的最小值,这个就可以用线段树维护了,所以d[x] = dis[ x ]+re[ x ]
对于修改的话,如果修改的边是树枝边,那么 v 以及 v 的子树的 dis 值都要发生改变;如果不是的话,那么只需要修改 u 的 w 值,这些也可以用线段树来做,就是区间修改
然后代码中判断是否为祖先我用的是倍增法
时间复杂度O()
代码
#include<cstdio>
#include<cstring>
#include<cmath>
#include<iostream>
#include<algorithm>
#define N 1000009///////////虽然我也不知道为什么要开那么大,反正开小了会RE,啊,我又知道了,你们就自己看吧
#define in read()
#define lc (k<<1)
#define rc (k<<1)|1
#define ll long long
#define inf 1e18
using namespace std;
int head[N],nxt[N],to[N],tot,from[N];
int st[N],end[N];
int fa[N][20],dep[N];
ll minn[4*N],lazy[4*N],d[N],w[N],dis[N],re[N];
void add(int x,int y,int z){nxt[++tot]=head[x];head[x]=tot;to[tot]=y;from[tot]=x;w[tot]=z;}
(为了不让代码显得太长,我就把读优省略了)
int n,q,dfn=1;
void getfn(int u){
for(int e=head[u];e;e=nxt[e]){
int v=to[e];
if(v==1||dep[v]) continue;
st[v]=++dfn;//dfs序
dis[v]=dis[u]+w[e];//从1到i通过树枝边的距离
d[st[v]]=dis[v]+re[v];//注意这个地方是直接记录在该点的dfn上,因为线段树的时候下标是这个点的dfs序
fa[v][0]=u;dep[v]=dep[u]+1;//处理倍增需要用的fa和dep
getfn(v);
}
end[u]=dfn;//两种写法都可以,只是换另一种写法的时候要注意初始
/*int i,j;
st[x]=++dfn;
d[st[x]]=ww+re[x];
for(i=head[x];i;i=nxt[i])
{
j=to[i];
if(j==1||dep[j]) continue;
fa[j][0]=x;
dep[j]=dep[x]+1;
getfn(to[i],ww+w[i]);
}
end[x]=dfn;*/
}
void build(int k,int l,int r){
if(l==r){
minn[k]=d[l];//我们要的是区间的最小值
return;
}
int mid=l+r>>1;
build(lc,l,mid);build(rc,mid+1,r);
minn[k]=min(minn[lc],minn[rc]);
}
void pushdown(int k){
lazy[lc]+=lazy[k];
minn[lc]+=lazy[k];
lazy[rc]+=lazy[k];
minn[rc]+=lazy[k];
lazy[k]=0;
}
void modify(int k,int l,int r,int x,int y,int z){
if(x<=l&&r<=y){
minn[k]+=z;
lazy[k]+=z;
return;
}
if(lazy[k]) pushdown(k);
int mid=l+r>>1;
if(x<=mid) modify(lc,l,mid,x,y,z);
if(y>mid) modify(rc,mid+1,r,x,y,z);
minn[k]=min(minn[lc],minn[rc]);
}
int lca(int x,int y){/////////////////
if(dep[x]<dep[y]) swap(x,y);
for(int i=17;i>=0;--i)
if(dep[fa[x][i]]>=dep[y]) x=fa[x][i];
if(x==y) return x;//这个地方返回祖先,如果与该次询问起点相同,才可以直接从起点往终点走
for(int i=17;i>=0;--i)
{
if(fa[x][i]!=fa[y][i])
{
x=fa[x][i];
y=fa[y][i];
}
}
return fa[x][0];//////////////////
}
ll query(int k,int l,int r,int x,int y){
if(x<=l&&r<=y) return minn[k];
ll res=inf;
int mid=l+r>>1;
if(lazy[k]) pushdown(k);
if(x<=mid) res=min(res,query(lc,l,mid,x,y));
if(y>mid) res=min(res,query(rc,mid+1,r,x,y));
return res;
}
int main(){
n=in;q=in;
int i,j,k,x,y,z;
for(i=1;i<n;++i){
x=in;y=in;z=in;
add(x,y,z);
}
for(i=1;i<n;++i){
x=in;y=in;z=in;
add(x,y,z);
re[x]=z;
}
fa[1][0]=0;dep[1]=1;st[1]=1;
getfn(1);
build(1,1,n);
for(j=1;j<=17;++j)
for(i=1;i<=n;++i)
fa[i][j]=fa[fa[i][j-1]][j-1];
for(i=1;i<=q;++i){
x=in;y=in;z=in;
if(x==1){
int v=to[y];
if(v==1) modify(1,1,n,st[from[y]],st[from[y]],z-re[from[y]]),re[from[y]]=z;/////////v==1的时候相当于单点修改,然后不能忘了随时修改更新re
else modify(1,1,n,st[v],end[v],z-w[y]),w[y]=z;///////////更新w不能忘
}
else{
if(lca(y,z)==y) {
printf("%lld\n",query(1,1,n,st[z],st[z])-re[z]-query(1,1,n,st[y],st[y])+re[y]);//////////这里不能直接用dis[y]-dis[z],因为dis始终记录的是最开始的状态,中途的修改都没有更新过它,我们需要重新询问
}
else{
printf("%lld\n",query(1,1,n,st[y],end[y])-query(1,1,n,st[y],st[y])+re[y]+query(1,1,n,st[z],st[z])-re[z]);/////////////这个和上面那个是同样的道理
}
}
}
return 0;
}
综上所述:
我被这道题卡死的地方就是因为我忘了更新!!!