P2934 [USACO09JAN]Safe Travel G

题目

给定一个无向图,保证从1号点到所有点的最短路只有一条,现在对于每一个点询问:如果把1号点到这个点的最短路径的最后一条边断掉,那么到这个点的最短路变成了多少?

分析

首先我们发现最短路只有一条,其实就是在提示我们建出最短路树。

那么现在每一条树外面的边其实就有各自的贡献,对于一条边,它可以更新(u o lca)(v o lca) 的路径上的每一个点 (i),值为 (val(u,v)+dis_u+dis_v-dis_i)

我们发现直接去修改更新这些点的答案似乎并不好做。

但是有这样的一个性质:对于每一个点来说,其本身被哪些边更新完了,上面柿子的最后一项是确定的,无论是什么边来更新这个点的答案,后面的那一项都是个定值。

那么现在我们的困难就解决了,我们难以修改就是因为最后的一项是个变量,对于所有点来说不一样,就不好修改,而现在我们可以先忽略这个,那么我们就可以只维护前面的部分了。

于是对于每一个点的答案,我们贪心地去更新,也就是把所有边按照(val(u,v)+dis_u+dis_v)排序,然后更新路径,再把路径上的所有点用并查集合并起来,因为这些点的答案已经确定了,之后不会再被用到。

代码

#include<bits/stdc++.h>
using namespace std;
template <typename T>
inline void read(T &x){
	x=0;char ch=getchar();bool f=false;
	while(!isdigit(ch)){if(ch=='-'){f=true;}ch=getchar();}
	while(isdigit(ch)){x=x*10+(ch^48);ch=getchar();}
	x=f?-x:x;
	return ;
}
template <typename T>
inline void write(T x){
	if(x<0) putchar('-'),x=-x;
	if(x>9) write(x/10);
	putchar(x%10^48);
	return ;
}
#define ll long long
#define int long long
const int N=7e5+5,INF=1e12+7;
int n,m;
int head[N],to[N<<1],nex[N<<1],val[N<<1],fr[N<<1],idx=1;
inline void add(int u,int v,int w){
	nex[++idx]=head[u];
	fr[idx]=u;
	to[idx]=v;
	val[idx]=w;
	head[u]=idx;
	return ;
}
#define PII pair<int,ll>
priority_queue<PII,vector<PII>,greater<PII> >q;
int pre[N],num[N];
ll dis[N];
bool vis[N<<1];
void Dijkstra(){
	memset(dis,0x3f,sizeof(dis));
	dis[1]=0;q.push(make_pair(0,1));
	while(!q.empty()){
		PII t=q.top();q.pop();
		int x=t.second;ll d=t.first;
		if(vis[x]) continue;vis[x]=true;
		for(int i=head[x];i;i=nex[i]){
			int y=to[i];
			if(dis[y]>dis[x]+val[i]) dis[y]=dis[x]+val[i],q.push(make_pair(dis[y],y)),pre[y]=x,num[y]=i;
		}
	}
	return ;
}
vector<int>vec[N];
ll Ans[N];
int top1;
struct Edge{
	int u,v,val,lca;
	Edge(int u=0,int v=0,int val=0,int lca=0):u(u),v(v),val(val),lca(lca){}
	inline bool operator < (const Edge &B)const{return val<B.val;}
}sta[N<<1];
inline void cmin(ll &x,int y){if(y<x) x=y;}
int dep[N],siz[N],fa[N],son[N],top[N];
void dfs1(int x,int f){
	dep[x]=dep[f]+1,siz[x]=1,fa[x]=f;
	for(auto y:vec[x]){
		dfs1(y,x);
		siz[x]+=siz[y];
		if(siz[y]>siz[son[x]]) son[x]=y; 
	}
	return ;
}
void dfs2(int x,int f){
	if(x==son[fa[x]]) top[x]=top[fa[x]];
	else top[x]=x;
	if(son[x]) dfs2(son[x],x);
	for(auto y:vec[x]) if(y!=son[x]) dfs2(y,x);
	return ;
}
inline int QueryLca(int u,int v){
	while(top[u]!=top[v]){
		if(dep[top[u]]<dep[top[v]]) swap(u,v);
		u=fa[top[u]];
	}
	return dep[u]<dep[v]?u:v;
}
int fa1[N];
int Getfa(int x){return x==fa1[x]?x:fa1[x]=Getfa(fa1[x]);}
void Solve(int x,int Rt,int v){
	if(x==Rt||!x) return ;
	if(!vis[x]) Ans[x]=v-dis[x],vis[x]=true;
	if(fa[Getfa(fa[x])]!=Rt) fa1[x]=Getfa(fa[x]);
	Solve(Getfa(fa[x]),Rt,v); 
	return ;
}
signed main(){
//	system("fc my.out P2934_6.out");
//	freopen("P2934_6.in","r",stdin);
//	freopen("my.out","w",stdout);
	read(n),read(m);
	for(int i=1;i<=m;i++){
		int u,v,w;
		read(u),read(v),read(w);
		add(u,v,w),add(v,u,w);
	}
	Dijkstra();
	memset(vis,0,sizeof(vis));memset(Ans,0x3f,sizeof(Ans));
	for(int i=1;i<=n;i++) vec[pre[i]].push_back(i),vis[num[i]]=vis[num[i]^1]=true;
	dfs1(1,0),dfs2(1,0);
	for(int i=2;i<=idx;i++){
		if(vis[i]||vis[i^1]) continue;
		vis[i]=true;sta[++top1]=Edge(fr[i],to[i],dis[fr[i]]+dis[to[i]]+val[i],QueryLca(fr[i],to[i]));
	}
	memset(vis,0,sizeof(vis));
	for(int i=1;i<=n;i++) fa1[i]=i;
	sort(sta+1,sta+top1+1);
	for(int i=1;i<=top1;i++){
		int x=Getfa(sta[i].u),y=Getfa(sta[i].v);
        while(x!=y){
            if(dis[x]<dis[y]) swap(x, y);
            Ans[x]=sta[i].val-dis[x];
            fa1[x]=fa[x];
            x=Getfa(x);
        }
	}
	for(int i=2;i<=n;i++) write(Ans[i]>INF?-1:Ans[i]),putchar('
');
	return 0;
}

总结

这样的“维护过程中不好维护”的某一项,如果在对于其自身的答案这一项具有不变性的话,我们就可以先不考虑这一项,然后最后更新答案的时候再加进去即可。

这样的思路非常巧妙,需要注意。

原文地址:https://www.cnblogs.com/Akmaey/p/14932444.html