最短路的变式问题总结

1.[HAOI2012]道路 ​​​​​​ 

题意:对于每条道路,求出有多少条最短路经过它

 思路:

(1)考虑求出其中一次最短路的贡献   先跑一遍最短路,得到一个最短路图,对于边(u,v,w),判断其是否为最短路的方法是检查dis[v]==dis[u]+w,若式子左边大于右边则v一定通过其他边松弛成功,(u,v)不在最短路上,若左边等于右边,则最短路经过此路径松弛 (若式子左边小于右边说明最短路写挂了

 (2)考虑如何统计   

我们需要处理出以一条路径(u,v)为最短路的次数即为起点到 u的最短路个数加上 v 到各个节点的最短路个数。由于最短路图为一个 DAG,考虑通过拓扑排序上dp来解决。

首先能做出贡献的点是起点,递推关系即可转化为最短路图上的边的关系,这样即可求出图中每一个点在起点构成的最短路图中的所对应最短路径的个数以及其拓扑序。

再利用拓扑反序求出每个点到其余点的最短路。这里之所以能用拓扑反序是因为最短路图在求拓扑序的时候已经将图给划分好层次了,这时再反过来遍历就不会出现其它情况 。这里要注意因为从起点到这个点本身也是最短路,所以这个值本身就要为一。

然后根据乘法原理,把一条边的左右两端点的对应权值相乘即为当前起点的最短路数,求 n遍值相加即可。

#include <bits/stdc++.h>
using namespace std;
const int mod=1e9+7;
int n,m,cnt,head[200005],ans[200005],dis[200005],rd[200005];
int f[200005],g[200005],tot,tpx[200005];
bool vis[200005],ok[200005];
struct node{int f,to,nxt,w;}e[200005];
void insert(int u,int v,int w){
	e[++cnt].nxt=head[u];e[cnt].f=u;e[cnt].to=v;e[cnt].w=w;head[u]=cnt;
}
void spfa(int s){
	for(int i=1;i<=n;i++)dis[i]=1e9,vis[i]=0;
	queue<int> q;
	dis[s]=0;vis[s]=1;q.push(s);
	while(!q.empty()){
		int u=q.front();q.pop();vis[u]=0;
		for(int i=head[u];i;i=e[i].nxt){
			int v=e[i].to;
			if(dis[v]>dis[u]+e[i].w){
				dis[v]=dis[u]+e[i].w;
				if(!vis[v])q.push(v),vis[v]=1;
			}
		}
	}
	for(int i=1;i<=m;i++){
		if(dis[e[i].f]+e[i].w==dis[e[i].to])ok[i]=1,rd[e[i].to]++;
		else ok[i]=0;
	}
}
void topo(){
	queue<int> q;
	for(int i=1;i<=n;i++)if(!rd[i])q.push(i);
	tot=0;
	while(!q.empty()){
		int u=q.front();q.pop();
		tpx[++tot]=u;
		for(int i=head[u];i;i=e[i].nxt){
			if(!ok[i])continue;
			int v=e[i].to;rd[v]--;
			if(!rd[v])q.push(v);
		}
	}
}
void sum(int s){
	memset(f,0,sizeof(f));memset(g,0,sizeof(g));
	f[s]=1;
	for(int i=1;i<=tot;i++){
		int u=tpx[i];
		for(int i=head[u];i;i=e[i].nxt){
			if(!ok[i])continue;int v=e[i].to;f[v]+=f[u];
		}
	}
	for(int i=tot;i>=1;i--){
		int u=tpx[i];g[u]++;
		for(int i=head[u];i;i=e[i].nxt){
			if(!ok[i])continue;
			int v=e[i].to;g[u]+=g[v];
		}
	}
	for(int i=1;i<=m;i++){
		if(ok[i])ans[i]=(ans[i]+f[e[i].f]*g[e[i].to])%mod;
	}
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1,u,v,w;i<=m;i++){
		scanf("%d%d%d",&u,&v,&w);
		insert(u,v,w);
	}
	for(int i=1;i<=n;i++)spfa(i),topo(),sum(i);
	for(int i=1;i<=m;i++)printf("%d\n",ans[i]);
	return 0;
}

2.[NOIP2017 提高组] 逛公园  ​​​​​​    

题意:从1-n有多少条路径长度不超过d+k (d为最短路长度)

#include <bits/stdc++.h>
using namespace std;
int n,m,k,p,head[100005],resav[100005],dis[100005],d,timber,tot;
int fa[100005],s[100005],top,low[100005],dfn[100005],t;
int f[100005][52],sz[100005],flag;
bool vis[100005],vist[100005][52],visit[100005];
priority_queue<pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > > q;
struct node{int to,nxt,w;}e[200005];
void insert(int u,int v,int w){
	e[++tot].nxt=head[u];e[tot].to=v;e[tot].w=w;head[u]=tot;
}
void dij(){
	vis[1]=dis[1]=0;q.push(make_pair(0,1));
	while(!q.empty()){
		int u=q.top().second;q.pop();
		if(vis[u])continue;vis[u]=1;
		for(int i=head[u];i;i=e[i].nxt){
			int v=e[i].to,w=e[i].w;
			if(dis[v]>dis[u]+w)dis[v]=dis[u]+w,q.push(make_pair(dis[v],v));
		}
	}
}
void tarjan(int u){
	dfn[u]=low[u]=++timber;s[++top]=u;visit[u]=1;
	for(int i=head[u];i;i=e[i].nxt){
		if(e[i].w)continue;
		int v=e[i].to;
		if(!dfn[v])tarjan(v),low[u]=min(low[u],low[v]);
		else if(visit[v])low[u]=min(low[u],dfn[v]);
	}
	if(low[u]==dfn[u]){
		while(top){
			int x=s[top--];visit[x]=0;
			fa[x]=u;sz[u]++;
			if(x==u)break;
		} 
	}
}
void check(){
	timber=0; for(int i=1;i<=n;i++)dfn[i]=low[i]=sz[i]=0,fa[i]=i;
	for(int i=1;i<=n;i++)if(!dfn[i])tarjan(i);
}
int dfs(int x,int y){
	if(y>k)return 0;
	if(vist[x][y])return f[x][y];
	vist[x][y]=1;f[x][y]=0;
	if(x==n)f[x][y]++;
	for(int i=head[x];i;i=e[i].nxt){
		int xp=dfs(e[i].to,y+dis[x]+e[i].w-dis[e[i].to]);
		if(xp&&sz[fa[e[i].to]]>1){flag=1;return 0;}
		f[x][y]=(f[x][y]+xp)%p;
	}
	return f[x][y]%p;
}
int main(){
	scanf("%d",&t);
	while(t--){
		scanf("%d%d%d%d",&n,&m,&k,&p);
		flag=0;memset(head,0,sizeof(head));tot=0;
		memset(vist,0,sizeof(vist));
		for(int i=2;i<=n;i++)vis[i]=0,dis[i]=1e9;
		for(int i=1,x,y,w;i<=m;i++)scanf("%d%d%d",&x,&y,&w),insert(x,y,w);
		check();dij(); int res=dfs(1,0);
		if(flag)printf("-1\n");
		else printf("%d\n",res);
	}
	return 0;
}

3.集合位置 - 洛谷

题意:求次短路

首先找出一条最短路,之后一次去掉一条最短路上的边,对于每一次去掉一条边后的图都跑一次最短路,其中最小的答案就是次短路。

为什么要去掉最短路上的一条边呢?因为次短路和最短路必然至少有一条边不是共有的。为什么不去掉不在最短路上的边呢?因为这样最短路是没有变化的。

#include <bits/stdc++.h>
using namespace std;
int n,m,cnt,head[50005],pre[50005],xx,yy,x[50005],y[50005];
double ans=1e9,dis[50005];
bool flag,vis[50005];
double distance(int ax,int ay,int bx,int by){
	return (double)sqrt((ax-bx)*(ax-bx)+(ay-by)*(ay-by));
}
struct node{int to,nxt;double w;}e[50005];
void insert(int u,int v,double w){
	e[++cnt].nxt=head[u];e[cnt].to=v;e[cnt].w=w;head[u]=cnt;
}
void dij(int xx,int yy){
	priority_queue<pair<double,int>,vector<pair<double,int> >,greater<pair<double,int> > >q;
	for(int i=1;i<=n;i++)dis[i]=1e9,vis[i]=0;
	dis[1]=0;vis[1]=0;q.push(make_pair(0,1));
	while(!q.empty()){
		int u=q.top().second;q.pop();
		if(vis[u])continue;vis[u]=1;
		for(int i=head[u];i;i=e[i].nxt){
			int v=e[i].to;
			if((u==xx&&v==yy)||(u==yy&&v==xx))continue;
			if(dis[v]>dis[u]+e[i].w){
				dis[v]=dis[u]+e[i].w;
				if(!flag)pre[v]=u;
				q.push(make_pair(dis[v],v));
			}
		}
	}
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++){scanf("%d%d",&x[i],&y[i]);}
	for(int i=1,p,q;i<=m;i++){
		scanf("%d%d",&p,&q);
		double w=distance(x[p],y[p],x[q],y[q]);
		insert(p,q,w);insert(q,p,w);
	}
	dij(-1,-1);flag=1;
    for(int i=n;i!=1;i=pre[i]){
    	dij(i,pre[i]);
    	ans=min(ans,dis[n]);
	}
	if(ans==1e9)printf("-1\n");
	else cout <<fixed<<setprecision(2)<<ans<<endl;
	return 0;
}

4. [PA2012]Tax - 洛谷    【差分优化最短路建图】

题意:一个无向图,经过一个点的代价是进入和离开这个点的两条边的边权的较大值,求从起点 1到点 n的最小代价

首先考虑暴力建图: 把无向边拆为两条,把边当作点,对于两条边a->b,b->c,在这两条边之间连有向边,边权为这两条边权值较大值 边数为(m^2)

思考:这种暴力建图的方式冗余在何处,从哪里入手优化

发现:实际上,一个点的很多边彼此有着联系:对于一条边a->b(设权值为w),b点连出的所有比w小的边都是w本身,而对于比w大的边,增长的值为两边的差值

所以,我们考虑用差分的思想,即利用差值,优化连边

做法:将原图每个点的出边排序,权值小的点向权值大的点连边,权值为差值,大的向小的连边,权值为0

新建源点s,汇点t,s向所有1的出边连边,所有连入n的边向t连边,求s->t的最短路即可

这样保证小边到大边,差分累加答案,大边到小边最大值仍是大边,权值为0

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int n,m,tot=1,cnt=1,h[200005],head[400005],tmp[200005],s,t;
ll dis[400005];
bool vis[400005];
struct node{int to,nxt,w;}ed[400005],e[2000005];
priority_queue<pair<ll,int>,vector<pair<ll,int> >,greater<pair<ll,int> > > q;
void add(int u,int v,int w){
    ed[++tot].nxt=h[u];ed[tot].to=v;ed[tot].w=w;h[u]=tot;
}
void insert(int u,int v,int w){
	e[++cnt].nxt=head[u];e[cnt].to=v;e[cnt].w=w;head[u]=cnt;
}
bool cmp(int a,int b){return ed[a].w<ed[b].w;}
void dij(){
	for(int i=2;i<=t;i++)dis[i]=1e16;
	q.push(make_pair(0,1));
	while(!q.empty()){
		int u=q.top().second;q.pop();
		if(vis[u])continue; vis[u]=1;
		for(int i=head[u];i;i=e[i].nxt){
			int v=e[i].to;
			if(dis[v]>dis[u]+e[i].w)
			  dis[v]=dis[u]+e[i].w,q.push(make_pair(dis[v],v));
		}
	}
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1,u,v,w;i<=m;i++)
		scanf("%d%d%d",&u,&v,&w),add(u,v,w),add(v,u,w);
	s=1,t=2*(m+1);
	for(int i=1;i<=n;i++){
		int top=0;
		for(int j=h[i];j;j=ed[j].nxt)tmp[++top]=j;
		sort(tmp+1,tmp+top+1,cmp);
		for(int j=1;j<=top;j++){
			int now=tmp[j],nxt=tmp[j+1];
			if(ed[now].to==n)insert(now,t,ed[now].w);
			if(i==1)insert(s,now,ed[now].w);
			insert(now^1,now,ed[now].w);
			if(j<top)insert(now,nxt,ed[nxt].w-ed[now].w),insert(nxt,now,0);
		}
	}
	dij(); printf("%lld\n",dis[t]);
	return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值