第一届“文翁杯”现场竞技赛 T6 障碍(最短路树)

传送门

【题目分析】

调了一晚上的题qwq。。。%%%zxy原创题啊tql%%%

先说说最短路,由于出题人极为毒瘤,他既卡了SPFA又卡了堆优化的dij(反正我也没写qwq堆优化只有10pts来着?),考虑一个点可能被多次松弛,所以堆里可能会有许多无用状态,所以只要卡卡这个堆的大小高度就会让一次操作的复杂度接近n但不满,所以考虑一个稳定复杂度的算法就是用set去代替堆。

然后考虑答案的统计。

不在最短路上的边,断了也不会对答案有影响,所以直接输出最短路长即可。

然后就是在最短路上的边,很明显不会枚举断边跑最短路,所以就有一个很有用的东西出来了:最短路树。

最短路树是基于原图建立的一棵树,以s为根,这棵树保证从父亲到儿子的路径权值和为父亲到儿子的最短路长度。反之从儿子到父亲亦然。但如果两者的lca不是两者中的一个的话就不满足这个性质。

显然,如果我们建出这样的一棵树,那么s->t的这条链上的任意边被删都会对答案有影响。

最优解的构造一定是这样的:s->u->v->t,其中u->v为非断边,s->u的最短路显然可以直接在最短路树上找到,但问题在于如何保证v->t,或者说t->v是最短路。

显然如果v是t的祖先的话可以直接在最短路树上找到,但如果不是,那么可以证明,t到v的最短路一定不会经过当前处理子树的连向父亲的边,因为如果经过再返回的的话就违背了最短路树的性质(因为爷爷到孙子的距离一定大于爸爸到孙子的距离)。

所以对于一条非树边u->v,其中u属于t所在连通块,v属于s所在连通块,将他的权值更新为disT[u]+w[i]+disS[v],那么假设当前处理的点为u,如果断掉u->fa[u]这条边,那么答案就变为set中维护的最小值。

(可能意识模糊的在口胡。。。如果有错请指出)

【代码~】

#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e5+10;
const int MAXM=5e5+10;
const int INF=0x3f3f3f3f;

int n,m,cnt=1,s,t;
int head[MAXN],disS[MAXN],disT[MAXN],vis[MAXN];
int nxt[MAXM],to[MAXM],w[MAXM];
int tedge[MAXM],fa[MAXN],dfn[MAXN],ys[MAXN],tot;
int pre[MAXN],ban[MAXN];
int ans[MAXM],in[MAXN],ins[MAXM];
int sta[MAXN],top;
struct Edge{
	int to,id;
};
vector<Edge> graph[MAXN];
set<pair<int,int> > se;

int Read(){
	int i=0,f=1;
	char c;
	for(c=getchar();(c>'9'||c<'0')&&c!='-';c=getchar());
	if(c=='-')
	  f=-1,c=getchar();
	for(;c>='0'&&c<='9';c=getchar())
	  i=(i<<3)+(i<<1)+c-'0';
	return i*f;
}

void add(int x,int y,int z){
	cnt++;
	nxt[cnt]=head[x];
	head[x]=cnt;
	to[cnt]=y;
	w[cnt]=z;
}

void dijkstra1(){
	memset(disS,INF,sizeof(disS));
	disS[s]=0;
	set<pair<int,int> >q;
	q.insert(make_pair(0,s));
	while(!q.empty()){
		int u=q.begin()->second;
		q.erase(q.begin());
		if(vis[u])
		  continue;
		for(int i=head[u];i!=-1;i=nxt[i]){
			int v=to[i];
			if(disS[v]>disS[u]+w[i]){
				q.erase(make_pair(disS[v],v));
				disS[v]=disS[u]+w[i];
				q.insert(make_pair(disS[v],v));
			}
		}
	}
}

void dijkstra2(){
	memset(disT,INF,sizeof(disT));
	disT[t]=0;
	set<pair<int,int> >q;
	q.insert(make_pair(0,t));
	while(!q.empty()){
		int u=q.begin()->second;
		q.erase(q.begin());
		for(int i=head[u];i!=-1;i=nxt[i]){
			int v=to[i];
			if(disT[v]>disT[u]+w[i]){
				q.erase(make_pair(disT[v],v));
				disT[v]=disT[u]+w[i];
				q.insert(make_pair(disT[v],v));
			}
		}
	}
}

void dfs1(int u,int f){
	vis[u]=1;
	dfn[u]=++tot;
	ys[tot]=u;
	fa[u]=f;
	for(int i=head[u];i!=-1;i=nxt[i]){
		int v=to[i];
		if(vis[v]||v==f)
		  continue;
		if(disS[v]==disS[u]+w[i]){
			dfs1(v,u);
			tedge[i>>1]=1;
			pre[v]=i>>1;
			graph[u].push_back((Edge){v,i>>1});
		}
	}
}

void dfs2(int u){
	sta[++top]=u;
	ban[u]=1;
	int siz=graph[u].size();
	for(int i=0;i<siz;++i){
		int v=graph[u][i].to;
		if(ban[v])
		  continue;
		dfs2(v);
	}
}

void solve(){
	for(int i=1;i<=m;++i)
	  if(!tedge[i])
	    ans[i]=disS[t];
	in[t]=1;
	for(int i=tot;i>1;--i){
		int u=ys[i];
		if(in[u]){
			in[fa[u]]=1;
			top=0,dfs2(u);
			for(int j=1;j<=top;++j){
				int p=sta[j];
				for(int x=head[p];x!=-1;x=nxt[x]){
					int v=to[x];
					if(tedge[x>>1])
					  continue;
					if(ban[v]){
						if(ins[x>>1])
						  se.erase(make_pair(disS[p]+w[x]+disT[v],x>>1));
					}
					else{
						se.insert(make_pair(disT[p]+w[x]+disS[v],x>>1));
						ins[x>>1]=1;
					}
				}
			}
			ans[pre[u]]=se.empty()?-1:se.begin()->first;
		}
		else{
			ans[pre[u]]=disS[t];
		}
	}
}

int main(){
	memset(head,-1,sizeof(head));
	n=Read(),m=Read();
	s=1,t=n;
	for(int i=1;i<=m;++i){
		int x=Read(),y=Read(),z=Read();
		add(x,y,z),add(y,x,z); 
	}
	dijkstra1();
	dijkstra2();
	if(disS[t]==INF){
		for(int i=1;i<=m;++i)
		  puts("-1");
		return 0;
	}
	dfs1(s,-1);
	solve();
	for(int i=1;i<=m;++i)
	  cout<<ans[i]<<'\n';
	return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值