首页 > 其他分享 >最短路相关

最短路相关

时间:2022-11-20 00:22:37浏览次数:69  
标签:val int 短路 Floyd 相关 删边 dis

0. 一些前置习题

1. luoguP1608 路径统计

实际上只需要开一个 cnt 记录一下到当前点的最短路有几条就行了.
跑 dijkstra 的时候, 如果是严格大于就直接把答案覆盖上, 等于就将方案数相加.

if(dis[p]==dis[u]+e[i].val)ans[p]+=ans[u];
if(dis[p]>dis[u]+e[i].val)
{
	dis[p]=dis[u]+e[i].val;
	ans[p]=ans[u];
	q.push(make_pair(dis[p],p));
}

2. luoguP1462 通往奥格瑞玛的道路

最大值最小, 考虑二分这个最大值.
对于当前二分的值 mid, 每次只保留点权小于等于 mid 的点跑一遍最短路, 判断最短路是否小于等于 b 即可.
总体非常简单, 代码略.

3. luoguP6545 [CEOI2014] The Wall

水 blog 是不好的, 所以说来一道厉害的题.

4. luoguP3640 [APIO2013] 出题人

奇怪的题.

1. 关于 Floyd 算法

众所周知 Floyd 算法实际上是一个很厉害的 dp.
有的时候我们就需要通过 Floyd 算法的本质来解决一些奇怪的问题.

0. Floyd 的原理

首先我们简单解释一下 Floyd 算法是如何工作的.

for(int k=1;k<=n;k++)
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
			dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);

实际上虽然说这里的 dis 好像就表示了从 \(i\) 到 \(j\) 的最短距离, 但其实 dis 数组有一维被省去了, 实际上应为 \(dis_{k,i,j}\), 表示从 \(i\) 到 \(j\) 只经过 \(1,2,\cdots,k\) 的最短距离.
相应地, 状态转移方程实际上也应该是:

\[dis_{k,i,j}=\min\{dis_{k-1,i,j},dis_{k-1,i,k}+dis_{k-1,k,j}\} \]

这样 Floyd 的原理就变得清晰了. 我们通过先在外层枚举 \(k\) 将这一维压掉了.

一道例题: luoguP1119 灾后重建

就是上面对 Floyd 理解的直接应用. 对于每个询问 \(T\), 我们跑只经过重建时间小于等于 \(T\) 的点的 Floyd 即可.

for(int i=1;i<=q;i++)
{
    int x=read(),y=read(),T=read();
    while(t[now]<=T&&now<n)
    {
        for(int u=0;u<n;u++)
            for(int v=0;v<n;v++)
                a[u][v]=min(a[u][v],a[u][now]+a[now][v]);
        now++;
    }
    if(t[x]>T||t[y]>T||a[x][y]==inf)printf("-1\n");
    else printf("%d\n",a[x][y]);
}

1. 传递闭包

说人话: 求出从每个点能到达的所有点.

很明显我们只需要将 Floyd 算法稍加改造就行.

for(int k=1;k<=n;k++)
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
			map[i][j]=map[i][k]&map[k][j];

2. Floyd 最小环

Floyd 可以用来求无向图最小环.
核心代码只有下面几行:

for(int k=1;k<=n;k++)
{
    for(int i=1;i<k;i++)
        for(int j=i+1;j<k;j++)
            ans=min(ans,dis[i][j]+mp[i][k]+mp[k][j]);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
}

具体解释一下.
首先一个环至少要有三个点. 不妨设环上编号最大的点为 \(k\), 它在环上相邻的两个点为 \(i,j\), 不妨设 \(i<j\).
我们考虑枚举 \(i,j,k\), 这样包含 \((i,k),(k,j)\) 的最小环就为 \(dis(i,j)+(i,k)+(k,j)\).
但其实上面的式子还有一点问题. 我们必须保证 \(dis(i,j)\) 这一项里的路径不经过 \(k\).
于是我们考虑利用 Floyd 的转移顺序, 当枚举到 \(k\) 的时候, 在转移之前, 我们只计算了从 \(i\) 到 \(j\) 经过点 \(1,2,\cdots,k-1\) 所得到的的最短路径. 这样 \(k\) 及之后的部分就都被排除了. (注意到我们钦定了 \(k\) 是最大的, 因此比 \(k\) 大的也不需要统计)

3. 反 向 Floyd

名字是乱起的(
考虑 这个 奇奇怪怪的题.
首先我们发现不合法当且仅当存在 \(a_{i,j}>a_{i,k}+a_{k,j}\).
另外, 如果有 \(a_{i,j}=a_{i,k}+a_{k,j}\), 那就说明实际上我们是 \(i\rarr k\rarr j\), 因此 \(i\rarr k\) 这条边就可以没有.
剩下的情况都是至少要有一条对应权值的边的.

for(int k=1;k<=n;k++)
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
        {
            if(a[i][j]>a[i][k]+a[k][j])
            {
                printf("-1\n");
                return 0;
            }
            if((a[i][j]==a[i][k]+a[k][j])&&i!=k&&j!=k)flag[i][j]=1;
        }
for(int i=1;i<=n;i++)
    for(int j=1;j<=n;j++)
        if(!flag[i][j])
            ans+=a[i][j];
printf("%lld\n",ans/2);

2. 最短路径树

最短路径树 \((\text{Shortest Paths Tree, SPT})\) 就是从源点 \(s\) 到其它每个点的最短路径构成的树.

求法也很简单, 就是在跑 dijkstra 的时候记录一下是从哪条边转移过来的.

void dijkstra()
{
	memset(dis,0x7f,sizeof(dis));
	dis[s]=0;q.push(make_pair(0,s));
	while(!q.empty())
	{
		int u=q.top().second;q.pop();
		if(vis[u])continue;
		vis[u]=1;
		for(int i=h[u];i;i=e[i].nxt)
		{
			int p=e[i].to;
			if(dis[p]>=dis[u]+e[i].val)//这一句加等号的原因接下来会解释
			{
				dis[p]=dis[u]+e[i].val;
				pre[p]=i;//记录是从哪条边转移过来的
				q.push(make_pair(dis[p],p));
			}
		}
	}
}

注意到上面的代码在转移的时候多增加了一个等号.

这是因为一般来说我们都是求最小最短路径树 \(\text{MSPT}\), 即权值和最小的最短路径树.

要做到这一点, 只需要在扩展的时候选择到当前点 \(p\) 边权最小的边就行了.

注意到在 dijkstra 中到源点最短路长度小的点会先被扩展, 因此如果在之后又出现了一个点到当前点的最短路和之前的相同, 这个点到当前点的这条边的边权一定更小.

因此我们只需要加一个等号就能使得到的最短路径树的边权和最小了.

另外一点是为什么要记录前驱边而不是记录前驱点. 一方面是因为有很多题都需要按照边的编号进行输出方案, 记录前驱边会好写很多, 但更加重要的是如果有重边, 只记点不记边就会挂的很惨.

具体地, 对于双向边, 开始存边时令 \(\text{cnt=1}\), 那么最后链式前向星中的编号除以 \(2\) 下取整就是按照读入顺序边的编号.

板子题: CF545E

比较好的一道题是 CF1005F. 我们来讨论这道题的做法.

题意略. 很明显就是让我们统计最短路径树的个数. 因为边权都是 \(1\), 根据我们上面的写法, 我们能够遍历到所有可以选择的边.

于是只需要把

pre[p]=i;

改成

pre[p].push_back(i);

就行了. 方案数就是所有点的 pre 的数量乘起来.

关于输出答案:

很明显可以跑一遍 dfs.

void dfs(int u)
{
	if(u==n+1)
	{
		now++;
		for(int i=1;i<=m;i++)printf("%d",vis[i]);
		printf("\n");
		if(now==sum)flag=1;
		return;
	}
	for(int i=0;i<qwq[u].size();i++)
	{
		if(flag)return;
		if(vis[qwq[u][i]/2])continue;
		vis[qwq[u][i]/2]=1;
		dfs(u+1);
		vis[qwq[u][i]/2]=0; 
	}
}

3. 删边最短路

讨论两种情况, 无向正权图和有向无权图.
需要注意的是一般有向图上并没有复杂度正确的算法.

1. 无向正权图删边最短路

只有删边的 模板题

实际上可以加强一下, 不只是删边, 每次更改一条边的权值也能做.
删边就可以看成是把一条边的权值改成 \(\inf\).
直接看题: CF1163F Indecisive Taxi Fee

简明题意: 给定一个无向图, 每次修改一条边的权值 (互相独立) 并询问 \(1\) 到 \(n\) 的最短路的长度.

我们讨论几种情况.

  1. 将不是最短路上的边的权值增大.
    显然答案还是原来的最短路.
  2. 将不是最短路上的边的权值减小.
    预处理每个点到 \(1\) 和 \(n\) 的最短路的长度 \(dis(1,u), dis(u,n)\).
    设边为 \((u,v)\), 答案就是原来的最短路长度, \(dis(1,u)+w'(u,v)+dis(v,n)\), \(dis(1,v)+w'(u,v)+dis(u,n)\) 三者的最小值.
  3. 将最短路上的边的权值减小.
    显然答案为原来最短路的长度减去减小量.
  4. 将最短路上的边的权值增大.
    此时答案就为原来最短路修改后的长度和不经过修改边的最短路的最小值.
    问题来了怎么求出不经过修改边的最短路的最小值.
    我们反过来考虑, 对于任意一条边, 求出经过它的最短路可以不经过哪些部分.
    有一个简单的性质, 删边后的最短路一定包含一段最短路的前缀和一段最短路的后缀. 因此可以不经过的部分就是最短路上连续的一段.
    经过任意一条边的最短路是易求的, 就是情况 2 中的做法.
    于是就简单了, 我们如果能求出这条边对应的可以不经过的最短路的部分, 那么对于这部分上的任意一条边, 不经过它的最短路取一个 \(\min\) 就行了.
    但是要如何求出这条边对应的可以不经过的最短路的部分? 我们的做法是, 考虑每个点 \(u\), 求出从 \(1\) 到 \(u\) 的最短路离开最短路的位置 \(l(u)\) 和从 \(u\) 到 \(n\) 进入最短路的位置 \(r(u)\).
    注意到在以上定义中 \(l(u)\) 和 \(r(u)\) 可能是不唯一的. 我们考虑让 \(l(u)\) 尽量靠近 \(1\), \(r(u)\) 尽量靠近 \(n\), 这样包含的最短路的边就会尽量多.
    要求出 \(l(u)\) 和 \(r(u)\), 我们只需要仿照最短路树的思路, 按照最短路的顺序进行 dp 即可.
    具体地, 以求 \(l(u)\) 为例, 若 \(dis(1,u)+w(u,v)=dis(1,v)\), 则令 \(l(v)=\min(l(u),l(v))\).
    需要注意, 为了避免回到最短路上, 不要用最短路上的边 \((u,v)\) 更新 dp 值.同时, 初始化为对最短路上的点, \(l(u)=u\).
    另外 dp 的时候就不用写最短路了, 按照 \(dis(1,u)\) 的顺序跑就行了.
    于是这道题就基本上做完了. 对于每条边, 它对应的区间就是 \([l(u),r(v)]\) 和 \([l(v),r(u)]\).
    至于维护区间 \(\min\) 和单点求值, 开一棵线段树就行了.

温馨提示: CF 上的数据非常毒瘤, 保证了 \(1\) 至 \(n\) 连通但未保证整张图是连通的, 需要注意.

#define int long long
//建图和 dijkstra 略去
inline int dis(int i)//经过 i 这一条边的最短路
{
	int uu=eds[i].u,vv=eds[i].v,vval=eds[i].val;
	if(dis1[uu]==inf)return inf;//特判不连通
	return vval+min(dis1[uu]+disn[vv],dis1[vv]+disn[uu]);
}
bool cmp1(int xx,int yy){return dis1[xx]<dis1[yy];}
bool cmpn(int xx,int yy){return disn[xx]<disn[yy];}
//线段树略去
signed main()
{
	//建图略
	dijkstra1();dijkstran();//先求 dis(1,u) 和 $dis(u,n)$ 并求出最短路
	memset(l,0x7f,sizeof(l));
	memset(r,0x7f,sizeof(r));
	for(int u=1;pre[u];u=e[pre[u]^1].to)//标记出最短路
	{
		path[++pcnt]=u;
		ispath[pre[u]]=ispath[pre[u]^1]=1;
	}
	path[++pcnt]=n;
	for(int pos=1;pos<=pcnt;pos++)//dp 预处理边界
	{
		int u=path[pos];
		l[u]=pos;r[u]=pos-1;//这里的 l,r 是最短路的边对应的区间的端点, 因此 r 要减 1
	}
	for(int i=1;i<=n;i++)order[i]=i;
	sort(order+1,order+n+1,cmp1);//按照 dis(1,u) 的顺序进行 dp
	for(int now=1;now<=n;now++)
	{
		int u=order[now];
		if(dis1[u]==inf)break;//不连通特判
		for(int i=h[u];i;i=e[i].nxt)
		{
			if(ispath[i])continue;//不用最短路更新 dp 值
			int p=e[i].to;
			if(dis1[p]==dis1[u]+e[i].val)l[p]=min(l[p],l[u]);
		}
	}
	//计算 r 的部分和 l 基本一样, 略去
	build(1,1,pcnt-1);
	for(int now=1;now<=m;now++)//枚举每条边贡献到线段树上
	{
		int i=2*now;//建的是双向边
		if(ispath[i])continue;
		int uu=eds[i].u,vv=eds[i].v;
		if(dis1[uu]==inf)continue;
		int ll=l[uu],rr=r[vv];
		if(ll<=rr)modify(1,ll,rr,dis(i));
		ll=l[vv];rr=r[uu];
		if(ll<=rr)modify(1,ll,rr,dis(i));
	}
	for(int i=1;i<=Q;i++)//分类讨论输出
	{
		int t=read(),x=read();t*=2;
		if(ispath[t])
		{
			if(x>e[t].val)
			{
				int uu=eds[t].u,vv=eds[t].v,now=min(l[uu],l[vv]);
				printf("%lld\n",min(dis1[n]-e[t].val+x,query(1,now,now)));
			}
			else printf("%lld\n",dis1[n]-e[t].val+x);
		}
		else
		{
			if(x>e[t].val||dis(t)==inf)printf("%lld\n",dis1[n]);
			else printf("%lld\n",min(dis1[n],dis(t)-e[t].val+x));
		}
	}
	return 0;
}

2. 有向无权图删边最短路

参考资料:
EI 的博客

有向图上的删边最短路做起来要困难得多. 不过如果有向图是无权的 (也可以认为边权是 \(1\)), 我们有一个期望时间复杂度不错的随机化算法.
首先根据前面无向图删边最短路的铺垫, 我们知道有这样的结论:

  • 考虑求出 \(s\) 到 \(t\) 的最短路, 那么我们只需要处理这条最短路上的删边就行.
  • 如果最短路上的边被删掉了, 那么我们在中间会从原来的最短路上离开, 并且因为最短路的性质和每次只删一条边, 我们只会离开一次.

我们约定一些记号: 记一开始求出的最短路为 \(p_1\rarr p_2\rarr\cdots\rarr p_k\).

(未完待续, 可能会长期不填)

4. 次短路和 k 短路

1. 次短路

这里有两种情况.

1. 可重复经过点和边

P2865 [USACO06NOV]Roadblocks G
什么是可重复经过点和边?
比如说我们有这样一个图:
image
这个时候从 \(1\) 到 \(2\) 的次短路是 \(3\) 而不是 \(4\), 路径 \(1\rarr2\rarr1\rarr2\) 是最短的.
但是我们发现显然最多只会有一次折返, 我们只需要每次枚举边 \((u,v)\), 若有 \(dis(1,u)+(u,v)+dis(v,n)\) 大于最短路长度, 就将 \(1\rarr u\rarr v\rarr n\) 统计为一个可能的次短路.
代码极其好写.

dijkstra1();dijkstran();shortest=dis1[n];
for(int u=1;u<=n;u++)
    for(int i=h[u];i;i=e[i].nxt)
    {
        int p=e[i].to;
        if(shortest==dis1[u]+disn[p]+e[i].val)continue;
        ans=min(ans,dis1[u]+disn[p]+e[i].val);
    }

好像还有一种在跑最短路的过程中求次短路的方法, 不过又难写又容易保证不了正确性和时间复杂度. (反正 luogu 上的题解一车 SPFA)

2. 不能重复经过点和边

就是 这道题, 虽然题面没说.
我们只需要每次删掉最短路上的一条边跑最短路即可.
还是很好写的(

dijkstra1();
int u=n;
while(u!=1)
{
    int i=pre[u];
    e[i].del=e[i^1].del=1;
    dijkstra2();ans=min(ans,dis[n]);
    e[i].del=e[i^1].del=0;
    u=prever[u];
}

2. k短路

咕咕咕.
哦对了 k 短路就不要求是简单路了, 因为若要求是简单路则没有时间复杂度正确的算法.

5. 网格图最短路

标签:val,int,短路,Floyd,相关,删边,dis
From: https://www.cnblogs.com/pjykk/p/16548644.html

相关文章

  • java 操作系统相关参数获取
    importjava.io.BufferedReader;importjava.io.File;importjava.io.IOException;importjava.io.InputStream;importjava.io.InputStreamR......
  • 登录注册相关实现
    目录实现一个网页未登录时自动跳转至登录界面?用户名与密码正确后登录又如何跳转至首页(或者其他页面)?实现一个网页未登录时自动跳转至登录界面?在地址栏输入url之后,如果当前......
  • cmake相关
    cmake使用时配置python路径关于pythonempy报错的问题,手动指定python3路径来解决。catkinconfig-DPYTHON_EXECUTABLE=/usr/bin/python3catkinbuildufomap_*--cm......
  • 【mysql】关于python建立mysql相关操作
    1.安装用pip安装指令pipinstallpymysql查看安装成功#cmdpipshowmysql#cmd找list中有该软件piplist#python中不报错importpymysql2.操作流程3.封装......
  • 二分图相关知识+染色法+匈牙利
    一、相关概念:1、二分图把图中的点分到两个集合中,集合内的点之间没有边相连,边存在于两个集合之间2、匹配、最大匹配、完美匹配匹配:匹配是边的集合,任意两条边都没有公共......
  • mybatis学习第⼆部分:Mybatis相关概念
    2.1 对象/关系数据库映射(ORM)ORM全称Object/Relation Mapping:表示对象-关系映射的缩写ORM完成⾯向对象的编程语⾔到关系数据库的映射。当ORM框架完成映射后,程序员既可......
  • web相关概念回顾、服务器软件_概述
    web相关概念回顾软件架构:C/S:客户端/服务器端B/S:浏览器/服务器端资源分类:静态资源:所有用户访问后,得到的结果都是一样的,称为......
  • Day02_计算机相关知识
    计算机Computer:全称电子计算机,俗称电脑能够按照程序运行,自动、高速处理海量数据的现代化智能电子设备由硬件和软件所组成常见的形式有台式计算机、笔记本计算......
  • 子进程相关基础知识
    昨日内容回顾粘包问题及解决思路粘包问题:TCP协议下将人认知中应该分来的数据打包发送,导致所谓粘包问题。解决思路:明确应接收数据的长度(至少首次收到的数据长度应明......
  • linux时间和当前时间相关8小时问题
    依次执行如下的代码: 1、更改时区cp/usr/share/zoneinfo/GMT/etc/localtimeln-sf/usr/share/zoneinfo/Asia/Shanghai  /etc/localtime 2、读取硬件时间到系......