[USACO09JAN]Safe Travel G

[USACO09JAN]Safe Travel G

​ 题目链接:[USACO09JAN]Safe Travel G

​ 又是一道初见杀的题目,这道题是跟最短路树有关。何为最短路树?

​ 考虑一个连通无向图 \(G\),一个顶点 \(v\) 为根节点的最短路径树 \(T\) 是图 \(G\) 满足下列条件的生成树——树 \(T\) 中从根节点 \(v\) 到其他顶点 \(u\) 的路径距离,在图 \(G\) 中是 \(v\)\(u\) 的最短路径距离。

​ 这个定义还是比较好理解的,那么我们怎么构造出最短路径树呢?既然跟最短路有关,我们先用 Dijkstra 算法预处理出图 \(G\)\(v\) 节点到其他所有顶点 \(u\) 的最短距离。然后对于一条边 \(E\) 设它两端的节点为 \(x,y\),边权值为 \(w\) 如果 \(dis_x+w=dis_y\) 就说明在最短路径树中 \(x\)\(y\) 的父亲节点。这样在本题中是正确的。因为据题意描述,根节点 \(v\) 到任意一个顶点 \(u\) 的路径是唯一的。这么连,首先它是连通的,因为每个节点的最短路径都是存在的。其次,一共有 \(n\) 个节点,我们对除了根节点 \(v\) 外的每个节点都找了一个父亲,所以有 \(n-1\) 条边。那么我们这么构造出来的就是一棵最短路径树了。

​ 构造出一棵最短路径树后,我们来试着添加无用边,这里的无用边指的是题目中给出,但最短路径树中没用到的边。首先看下图。

WhPevF.png

​ 图中的红边是一条无用边。边权为 \(w\),而两端节点为 \(x,y\) 我们来考虑下怎么更新 \(u\) 点的答案。如果要更新 \(u\) 点的话那么只能通过无用边走过去。通过最短路径树的定义可知。从根节点到一个顶点 \(u\) 的距离为 \(dis_u\)。那么我们可以通过这个性质可知,\(u\)\(y\) 的距离就为 \(dis_y-dis_u\)。那么我们就可以得知:

\[ans_u=\min(ans_u,dis_x+w+dis_y-dis_u) \]

​ 所以我们可以知道,我们可以用这条边,将图中这个环所有的除了 LCA 外的点全部更新。为什么LCA不能更新呢,因为 LCA 最短路径的最后一条边不在这个子树里了。

​ 但是这么更新我们的时间是肯定承受不住的。因为我们重复更新了许多点。那能不能让每个点只更新一次呢,我们考虑将边排序,对于更新的点 \(u\) 来说 \(dis_u\) 是不变的,我们考虑对于每条边\(E\),它两边的节点为 \(x,y\),边权为 \(w\)。我们按照 \(dis_x+dis_y+w\) 从小到大进行排序,这样我们使得每个点在第一次更新时就是它的最小值。

​ 但是我们在更新时,依然会遍历无用的点导致时间复杂度丝毫没有下降,那么我们得跳过已经更新过的点,这个时候我们可以使用一个常规的套路并查集缩点

WhAbf1.png

​ 考虑一个一维数组,我们将遍历过一次的点 \(i\) 的祖先设为 \(i+1\)。然后每次访问我们都访问这个节点的祖先,效果像图中所示,假设 2,3都已经被遍历过,我们从1开始遍历,遍历到2时,由于访问的是它的祖先节点,所以直接会跳到4去,这样我们就实现了不重复遍历的目的。

​ 一维数组并查集缩点伪代码如下:

for(int i=1;i<=n;i=find(i+1))
{
	.....//操作
	f[i]=i+1; 
}

​ 并查集缩点的优势就在于多次遍历同一段不会重复,我们将一维数组想像成一条链运用在树上,这题就成了,树上并查集缩点代码如下:

	for(int i=1;i<=m;++i)
	{
		int u=edge[i].u,v=edge[i].v,w=edge[i].w;
		if(dis[u]+w==dis[v]||dis[v]+w==dis[u]) continue;
		int x=find(u),y=find(v);
		while(x!=y)
		{
			if(dep[x]<dep[y]) swap(x,y);
			ans[x]=dis[u]+dis[v]+w-dis[x];
			f[x]=fa[x];
			x=find(x);
		}
	}

​ 如果 \(x\) 直接跳到 LCA 上面了会不会有问题呢?实际上并不会, \(x\) 跳到 LCA 上面说明 LCA 已经更新过了,这时候另一个点跳到 LCA 时就会与 \(x\) 重合。

​ 代码如下:

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1e5+5;
const int MAXM = 2e5+5;
const int INF = 0x3f3f3f3f;
int tot_E,head[MAXN],fa[MAXN],f[MAXN],dep[MAXN];
int find(int x)
{
	return f[x]==x?x:f[x]=find(f[x]);
}
struct E
{
    int to,w,pre;
}e[MAXM<<1];
void add(int u,int v,int w)
{
    e[++tot_E]=E{v,w,head[u]};
    head[u]=tot_E;
}
int dis[MAXN],n,m,ans[MAXN];
bool vis[MAXN];
struct node
{
    int p,dis;
    bool operator < (const node &x)const
    {
        return dis>x.dis;
    }
};
struct Edge
{
    int u,v,w;
    bool operator < (const Edge &x)const
    {
    	return dis[u]+dis[v]+w<(dis[x.u]+dis[x.v]+x.w);
	}
}edge[MAXM<<1];
void dij()
{
    priority_queue <node> q;
    q.push(node{1,0});
    memset(dis,0x3f,sizeof dis);
    dis[1]=0;
    while(!q.empty())
    {
        int p=q.top().p,dism=q.top().dis;
        q.pop();
        if(vis[p]) continue;
        vis[p]=1;
        for(int i=head[p];i;i=e[i].pre)
        {
            int to=e[i].to,w=e[i].w;
            if(dis[to]>dism+w)
            {
                dis[to]=dism+w;
                q.push(node{to,dis[to]});
            }
        }
    }
}
void dfs(int cur ,int father)
{
	fa[cur]=father;dep[cur]=dep[father]+1;
	for(int i=head[cur];i;i=e[i].pre)
	{
		int to=e[i].to,w=e[i].w;
		if(to==father||dis[cur]+w!=dis[to]) continue;
		dfs(to,cur);
	}
}
int main()
{
    scanf("%d %d",&n,&m);
    for(int i=1;i<=m;++i)
    {
        int a,b,t;
        scanf("%d %d %d",&a,&b,&t);
        add(a,b,t);add(b,a,t);
        edge[i].u=a,edge[i].v=b,edge[i].w=t;
    }
    memset(ans,0x3f,sizeof ans);ans[1]=0;
    dij();
    dfs(1,0);
	sort(edge+1,edge+1+m);
	for(int i=1;i<=n;++i) f[i]=i;
	for(int i=1;i<=m;++i)
	{
		int u=edge[i].u,v=edge[i].v,w=edge[i].w;
		if(dis[u]+w==dis[v]||dis[v]+w==dis[u]) continue;
		int x=find(u),y=find(v);
		while(x!=y)
		{
			if(dep[x]<dep[y]) swap(x,y);
			ans[x]=dis[u]+dis[v]+w-dis[x];
			f[x]=fa[x];
			x=find(x);
		}
	}
    for(int i=2;i<=n;++i)
    	printf("%d\n",ans[i]==INF?-1:ans[i]);
    return 0;
}

总结:最短路径树,普及组的知识点我居然第一次听到,还是见识狭小了 。以及在别的博客看到一句话还是比较有用的:如果题目给出最短路径唯一,那么十有八九与最短路径树有关。

posted @ 2021-07-26 21:33  夜空之星  阅读(36)  评论(0编辑  收藏  举报