题目
给定一个无向图,保证从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;
}
总结
这样的“维护过程中不好维护”的某一项,如果在对于其自身的答案这一项具有不变性的话,我们就可以先不考虑这一项,然后最后更新答案的时候再加进去即可。
这样的思路非常巧妙,需要注意。