洛谷 P9432 [NAPC-#1] rStage5 - Hard Conveyors

原题链接

这道题我看大家都用 dijkstra 啊,惊恐,这里提供一种换根 dp 的写法。

两点间最短路径,那一定是 LCA 没错了。用一遍 dfs 求出根节点到每个点的距离,记为 \(dist\)。那么 \(u,v\) 间最短路径长度的就是 \(dist_u+dist_v-dist_{\operatorname{lca}(u,v)}\times 2\)。
但是题目还要求至少经过一个关键点。如果 \(u,v\) 间的路径上存在关键点,那自然是好的,直接走就可以了;若不存在,则需要找一个路径上的点,使这个点向外走到一个关键点再回来的距离最小。
形式化地说,若路径长为 \(L\),每个点 \(i\) 到关键点的最近距离为 \(F_i\),则答案 \(ans=L+\min{F_i}\times 2\),其中点 \(i\) 在 \(u\) 到 \(v\) 的路径上。

之后就是实现了。我用的树剖加换根 dp。树剖求 LCA,再加线段树维护 \(F_i\) 的区间最小值,十分好写,这里就不赘述了,主要是 \(F\) 的求法。

  • 设 \(f_i\) 为以 \(i\) 点为根的子树中,点 \(i\) 与关键点的最近距离。这个十分好求,一遍 dfs 轻松解决。重要的是如何用第二次 dfs 进行换根。
  • 注意到 \(f\) 只包含了子树内的情况,那么就设 \(g_i\) 为 \(i\) 的子树外的关键点距离 \(i\) 的最近距离。有一个显然的转移:$$g_v=\min(g_v,g_u+edge_{u\to v})$$ 也就是从 \(u\) 的子树外转移而来。
  • 此外我们还有一种情况没有考虑,就是在 \(u\) 的子树内,但不在 \(v\) 的子树内,这时候 \(f\) 一维就不是很好转移了。我们修改为: 设 \(f_{i,1}\) 为以 \(i\) 为根的子树中,点 \(i\) 与关键点的最小距离;\(f_{i,0}\) 则为次小距离。
    这样一来,若当前 \(v\) 是那个最小值,就需要从次小值转移过来;否则从最小值转移。
    状态转移方程为:\(\begin{cases}g_v=\min(g_v,f_{u,0}+edge_{u\to v})&f_{v,1}+edge_{u\to v}=f_{u,1}\\g_v=\min(g_v,f_{u,1}+edge_{u\to v})&\operatorname{otherwise}\end{cases}\)

之后,显然 \(F_i=\min(g_i,f_{i,1})\),再用线段树维护最小值即可。

点击查看代码
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<string>
#include<utility>
#include<vector>
#include<queue>
#include<bitset>
#define int long long
#define FOR(i,a,b) for(register int i=a;i<=b;i++)
#define ROF(i,a,b) for(register int i=a;i>=b;i--)
#define mp(a,b) make_pair(a,b)
#define pll pair<long long,long long>
#define pii pair<int,int>
#define fi first
#define se second
using namespace std;
inline int read();
typedef long long ll;
const int N=1e5+5;
const int INF=0x3f3f3f3f;
int n,m,k,q;
struct E{
	int to,nex,w;
}edge[N<<1];
int head[N],cnt=0;
void add(int u,int v,int w){
	edge[++cnt]=(E){v,head[u],w};
	head[u]=cnt;
}
bitset<N>mark;
int son[N],fa[N],top[N],sz[N],dfn[N],dep[N],dist[N],F[N][2],g[N];
void dfs1(int u,int f){
	sz[u]=1;
	fa[u]=f;
	dep[u]=dep[f]+1;
	int max_son=-1;
	F[u][0]=F[u][1]=INF;
	if(mark[u]) F[u][0]=F[u][1]=0;
	for(int i=head[u];i;i=edge[i].nex){
		int v=edge[i].to;
		if(v==f) continue;
		dist[v]=dist[u]+edge[i].w;
		dfs1(v,u);
		if(F[v][1]+edge[i].w<F[u][1]){
			F[u][0]=F[u][1];
			F[u][1]=F[v][1]+edge[i].w;
		}
		else if(F[v][1]+edge[i].w<F[u][0]) F[u][0]=min(F[u][0],F[v][1]+edge[i].w);
		sz[u]+=sz[v];
		if(max_son<sz[v]) max_son=sz[v],son[u]=v;
	}
}
int tot=0,pos[N];
void dfs2(int u,int topx){
	dfn[u]=++tot;
	pos[tot]=min(F[u][1],g[u]);
	top[u]=topx;
	if(!son[u]) return ;
	dfs2(son[u],topx);
	for(int i=head[u];i;i=edge[i].nex){
		int v=edge[i].to;
		if(v==fa[u]||v==son[u]) continue;
		dfs2(v,v);
	}
}
int tr[N<<2];
void pushup(int u){
	tr[u]=min(tr[u<<1],tr[u<<1|1]);
}
void build(int u,int l,int r){
	if(l==r) return tr[u]=pos[l],void();
	int mid=l+r>>1;
	build(u<<1,l,mid);
	build(u<<1|1,mid+1,r);
	pushup(u);
}
int query(int u,int l,int r,int L,int R){
	if(L<=l&&r<=R)
		return tr[u];
	int mid=l+r>>1;
	int res=INF;
	if(L<=mid) res=min(res,query(u<<1,l,mid,L,R));
	if(R>mid) res=min(res,query(u<<1|1,mid+1,r,L,R));
	return res;
}
pii LCA(int u,int v){
	int res=INF;
	while(top[u]!=top[v]){
		if(dep[top[u]]<dep[top[v]]) swap(u,v);
		res=min(res,query(1,1,n,dfn[top[u]],dfn[u]));
		u=fa[top[u]];
	}
	if(dep[u]>dep[v]) swap(u,v);
	res=min(res,query(1,1,n,dfn[u],dfn[v]));
	return mp(u,res);
}
void dfs3(int u,int f){
	if(mark[u]) g[u]=0;
	for(int i=head[u];i;i=edge[i].nex){
		int v=edge[i].to;
		if(v==f) continue;
		g[v]=min(g[v],g[u]+edge[i].w);
		if(F[v][1]+edge[i].w==F[u][1]){
			g[v]=min(g[v],F[u][0]+edge[i].w);
		}
		else g[v]=min(g[v],F[u][1]+edge[i].w);
		dfs3(v,u);
	}
}
signed main()
{
	n=read(),q=read(),k=read();
	FOR(i,1,n-1){
		int u=read(),v=read(),w=read();
		add(u,v,w);
		add(v,u,w);
	}
	FOR(i,1,k){
		mark[read()]=1;
	}
	dfs1(1,0);
	memset(g,0x3f,sizeof g);
	g[1]=F[1][1];
	dfs3(1,0);
	dfs2(1,1);
	build(1,1,n);
	while(q--){
		int s=read(),t=read();
		if(s==t){
			printf("%lld\n",min(F[s][1],g[s])*2);
			continue;
		}
		pii temp=LCA(s,t);
		int lca=temp.fi,num=temp.se;
		int ans=dist[s]+dist[t]-dist[lca]*2+num*2;
		printf("%lld\n",ans);
	}
	return 0;
}


inline int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9') {if(ch=='-') f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
	return f*x;
}
posted @ 2023-10-19 21:40  LHLeisus  阅读(101)  评论(0)    收藏  举报