【2018/08/30】T3-图论-graph(SDOJ 3740)

版权声明:虽然我只是个小蒟蒻但转载也请注明出处哦 https://blog.csdn.net/weixin_42557561/article/details/82287892

题目描述:

给你一个图,一共有 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 

输出





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(n\cdot log\, n

代码

#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;
}

综上所述:
我被这道题卡死的地方就是因为我忘了更新!!!

猜你喜欢

转载自blog.csdn.net/weixin_42557561/article/details/82287892