最短路

Floyd算法

求图上i,j任意两点的最短路,按小图到全图的思想
想想一个图中所有灯都是灭的,逐个点亮灯,点亮第k盏灯时,用k重新更新i,j的最短路

\(dp[k][i][j]\) 代表已经用编号1~k的点来更新i,j的最短路径了
转移方程 $dp[k][i][j]=min(dp[k-1][i][k]+dp[k-1][k][j])
若i,j直连赋初值 \(dp[0][i][j]\) 就是边长,否则就是无穷大
最后可以滚掉k一维

floyd判负环

只需要在运算中出现任意 \(dp[i][i]<0\) ,就说明有负环
因为 \(dp[i][i]<0\) 是从i走到i,没有负环一定为0,在负环上的点更新后一定是负数

floyd传递闭包

若有条件满足i->j,j->k则i->k,并且现在给你一些条件,让你求任意两点的关系,就是此类问题,可以用floyd递推求解,复杂度 \(O(N^3)\) ,可以用bitset加速做到 \(O(N^2)\)

dijkstra

更好的理解
主要思想:每次确定一个点的最短距离
我们将图分为2块,一块为最短距离确定的点集,一块为没有确定最短距离的点集,通过前者向后者拓展,来求得答案
我们将所有已经有dis数值的点加入堆,然后每次dis数值最小的它的dis值就是最终的dis距离,所以可以将其加入到距离确定点集中,并用这个点去更新与它直接相连的点的dis值
为什么每次dis数值最小的它的dis值就是最终的dis距离呢?
因为如果其余的dis来更新这个最小距离的话,一定比当前的答案,更劣,因为当前dis已经是最小的了

为什么dij不能在负权边上使用

首先dij的证明基于一个三角形不等式的贪心

举例说明:

SPFA

可以用bfs的思路,一个点要用最小的dis去更新下一个点,所以如果有一个dis被更新了,就将其加入队列,直到没有点被更新就是队列为空

SPFA判负环

一个节点最多被更新n次,也就是入队n次因为最多有n个点会给它更新,如果更新次数超过n次则说明有负环

题目

P1613 跑路

很巧妙的一道floyd题,我们先预处理出所有距离为 \(2^t\) 的任意两点,设数组 \(dp[i][j][t]=1\) 表示在i,j中存在一条走 \(2^t\) 的路径,再将所有i,j为1的两点重新建图,边长为1,否则为无穷大,再跑一遍floyd即可

ybtoj

3.3.3

比较巧妙的一个转化,我们考虑枚举每一个点i,用从i到n的路径上的点权值最大值减去从1到i的路径上的点权值的最小值,然后统计答案即可
如何统计从1到i的路径上点的权值的最小值呢,就是可以用dij变一下形,每次在周围找点权最小的值加入集合s,再用它来更新周围点的最小值即可
然后从i到n的路径建反图跑即可(假了,dij不是干这个用的)
一张假的证明:

应该用缩点加topo统计一条路径上最小值

3.3.4

定义三个状态 \(dp[i][j][k]\) 表示走到i,j油箱剩余k时的最小花费,转移显然(ybt上讲的很明白),为什么不能直接dp呢,因为它可以向上移动,dp需要反复更新,就变成SPFA了,也可做,我们把每个状态看成一个点,边权就是转移的代价,就可以了
注意:有一个细节在我代码中有注释,因为这个想了好久

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=105,inf=1e17+5;
int n,k,a,b,c,x,y,z,w,ans=inf;
int oil[N][N],dp[N][N][15],vis[N][N][15];
int xx[4]={0,-1,0,1},yy[4]={-1,0,1,0};
struct Node{
	int x,y,z,w;
	bool operator < (const Node& b) const{
		return w>b.w;
	}
};
priority_queue<Node>q;
signed main(){
	scanf("%lld%lld%lld%lld%lld",&n,&k,&a,&b,&c);
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			scanf("%lld",&oil[i][j]);
			for(int g=0;g<=k;g++){
				dp[i][j][g]=inf;
			}
		}
	}
	q.push({1,1,k,0});
	dp[1][1][k]=0;
	while(!q.empty()){
		x=q.top().x,y=q.top().y,z=q.top().z,w=q.top().w;
		q.pop();
		if(vis[x][y][z])  continue;
		vis[x][y][z]=1;
		if(oil[x][y]&&k!=z){//因为z==k时,已经加完油了,应该用它来更新其余的点
			if(!vis[x][y][k]&&dp[x][y][k]>dp[x][y][z]+a){
				dp[x][y][k]=dp[x][y][z]+a;
				q.push({x,y,k,dp[x][y][k]});
			}
			continue;//注意有加油站则必须加油
		}
		else{
			if(!vis[x][y][k]&&dp[x][y][k]>dp[x][y][z]+a+c){
				dp[x][y][k]=dp[x][y][z]+a+c;
				q.push({x,y,k,dp[x][y][k]});
			}		
		}
		for(int i=0;i<=3;i++){
			int nx=x+xx[i],ny=y+yy[i];
			if(nx<1||ny<1||nx>n||ny>n)  continue;
			if(i<=1){
				if(z&&!vis[nx][ny][z-1]&&dp[nx][ny][z-1]>dp[x][y][z]+b){
					dp[nx][ny][z-1]=dp[x][y][z]+b;
					q.push({nx,ny,z-1,dp[nx][ny][z-1]});
				}
			}
			else{
				if(z&&!vis[nx][ny][z-1]&&dp[nx][ny][z-1]>dp[x][y][z]){
					dp[nx][ny][z-1]=dp[x][y][z];
					q.push({nx,ny,z-1,dp[nx][ny][z-1]});
				}
			}
		}
	}
	for(int i=0;i<=k;i++){
		ans=min(ans,dp[n][n][i]);
	}
	printf("%lld",ans);
}

3.3.5

floyd传递闭包板子题,上文有介绍

3.3.6

一眼二分,然后想到加一条边一定不会使路径变长,删边一定不会使路径变短,满足二分性
并且我们可以把1和n不连通的情况视为距离为inf,剩余情况好好想一想即可

3.3.7

粘一篇看懂了的题解

首先概括一下题意:求原点1到n的所有路中的第k+1长的路最小。

思考:为什么可以这样概括呢?

因为题意中的答案要最小,我们贪心肯定要使k次免费的资格用完,那么最划算的方案肯定是拿最长的k条路使之免费,然后付第k+1长路的长度的钱。。。

这样的贪心思路显然是正确的。

思路:我们首先二分第k+1长的路的长度(即答案),边界值l显然是0(因为有可能k条边就可以到达)、r是1000001(有可能本身图就不连通,若r==1000001,就直接-1)

然后关键是如何判断正确性。我们考虑简化问题,对于长度小于二分出的答案的线段,因为不需要付价钱,所以可以将其权值看作是0;

同理,大于二分的值的路径,我们将长度看作1(意味着我需要使用1次免费的资格)。

so,我们跑一遍最短路,看到了n点的最短路的长度,如果大于k,则不行,缩小r范围继续二分;如果小于,则有可能更小,缩小l范围继续二分

3.3.8

一眼分层图最短路,我们设 \(dp[i][k]\) 为走到第i号节点,使用了k次变为0

然后跑一遍dij即可

ps:这道题我之前没有调出来鸽了,后来补题时一发就过了,之前错误的原因是我的建图方式是把一个dp状态压成一维建图,然后但是我在遍历时用我压缩后的节点来访问我原来节点的下标,于是就错了

3.3.9

直接切了,二分然后用dij判断是否合法即可

posted @ 2024-10-29 19:43  daydreamer_zcxnb  阅读(7)  评论(0编辑  收藏  举报