最短路
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判断是否合法即可