P1216 ]数字三角形 (踢飞动规,最短路模板题)
题目大意:
解题思路:
很经典的一道动规题,各种算法教材都把这道题讲烂了,试问,学过动规的有几个没见过这道题?
虽然我一向很支持也很喜欢动规,但今天,我要一雪前耻,踢飞动规!
观察题目,使路径经过数字的和最大,裸裸的最短路标准提示语?如果不是教材,谁会想到这题要用动规呢?最短路水题啊!
这里简单阐述一下, 什么是最短路。
最短路分为两种,单源最短路和多源最短路。
单源最短路,就是说从一个起点到任一点的单个最短路径。
多源最短路,就是从任一点起点到任一点的多个最短路径。
简单来说,区别就在于对起点的规定,如果要求你每一个点都走一次最短路,有多个起点,那么就可以用多源最短路算法 Floyd 来做。如果只要求一个起点,那么就可以用 SPFA 算法和 Dijkstra 算法来做。
设某张图节点数为
n
n
n ,边数为
m
m
m,那么:
F
l
o
y
d
Floyd
Floyd 算法在多源最短路中的时间复杂度为
O
(
n
3
)
O(n^3)
O(n3),速度较慢,但处理多源最短路有良好的效果。
D i j k s t r a Dijkstra Dijkstra 算法在单源最短路中的时间复杂度 O ( n l o g ( n + m ) ) O(n\ log(n+m)) O(n log(n+m)) 速度相对较快,不过由于算法的性质,仅适用于处理只包含正数的最短路,对负数无能为力。
S P F A SPFA SPFA 算法时间复杂度为 O ( n m ) O(nm) O(nm),比 D i j k s t r a Dijkstra Dijkstra 稍微逊色,不过代码量短,并且可以处理负数,甚至判负环!
SPFA算法时间复杂度较慢,但是用处广,所以这里大概的讲一下:
设
u
u
u 表示当前所在的节点,
s
s
s 表示当前可以到达的某节点,
a
u
,
s
a_{u,s}
au,s 表示从点
u
u
u 到点
s
s
s 的边权,
d
i
s
u
dis_u
disu 表示从起点到
u
u
u 的最短路,现在我们要尝试更新
d
i
s
s
dis_s
diss ,找到从起点到
s
s
s 的更优路径。
也就是说,如果
d
i
s
u
+
a
u
,
s
<
d
i
s
s
dis_u+a_{u,s}\ <\ dis_s
disu+au,s < diss 的话,就用
d
i
s
u
+
a
u
,
s
dis_u+a_{u,s}
disu+au,s 更新
d
i
s
s
dis_s
diss 。
因此有状态转移方程:
d
i
s
s
=
m
i
n
(
d
i
s
u
+
a
u
,
s
,
d
i
s
s
)
dis_s=min\ (dis_u+a_{u,s}\ ,\ dis_s)
diss=min (disu+au,s , diss)
举一个栗子:
先看一下这张图。
找到一条从节点 2 到节点 3 的最短路径,让我们来模拟一下SPFA算法流程。
初始化
d
i
s
dis
dis 数组,全部设为正无穷。
首先我们知道,直接与节点2相邻的节点有 3,1,因此从节点2出发,尝试更新其他节点的
d
i
s
dis
dis
我们尝试从节点2走到节点3,代价为3,比
d
i
s
3
dis_3
dis3 所存的代价要小,所以
d
i
s
3
=
3
dis_3=3
dis3=3,更新
d
i
s
3
dis_3
dis3。
然后我们再从节点2走到节点1,代价为 -10,比
d
i
s
1
dis_1
dis1 小,因此更新
d
i
s
1
dis_1
dis1。
然后我们从节点1出发,可以去到节点 3,S,4。
尝试从节点1走到节点3,其代价为
d
i
s
1
+
4
dis_1+4
dis1+4,由于
d
i
s
1
=
dis_1=
dis1=-10,加上4以后比
d
i
s
3
dis_3
dis3 要小,因此用
d
i
s
1
+
4
dis_1+4
dis1+4 更新
d
i
s
3
dis_3
dis3。
在不能走回头路的情况下,很明显,从节点 2 走到节点 3 的最短路径长度是 -6,是按照这个路线走过来的:
2
−
>
1
−
>
3
2\ ->1\ ->3
2 −>1 −>3
但是,如果题目没有禁止走回头路的话,那么 SPFA算法就错了,因为,在节点 2 和节点1 出现了负数路径,如果我们在
2
−
>
1
−
>
2
2\ ->1\ ->2
2 −>1 −>2 这条路径反复横跳的话,那么路径长度会越来越小,SPFA就会出错。
但是还好,大部分良心题目都不会出现这种毒瘤情况,因为SPFA算法一般卡的是时间(到了那个时候建议用堆优化后的Dijkstra 算法),SPFA还是比较可靠的。(可惜他又死了)
切回正题,这题题目数据不大,行走的方向也只有两种,不会出现反复横跳的情况,因此我们可以考虑用 S P F A SPFA SPFA 算法(最短路和最长路其实就是一样的)。
用一个队列存起下一次展开探索的节点(标准的广搜思想),在每一次走的时候都尝试更新 d i s dis dis 数组,并判断松弛后的节点是否在队列中,如果不在则放入队列。
不一样的CODE:
#include <bits/stdc++.h>
using namespace std;
int R,a,ans=0;
int step[1001][1001];
int pictrue[1001][1001]={0};
struct Gar
{
int x,y;
Gar(int xx,int yy):x(xx),y(yy) {} //X表示行,Y表示列,坐标结构体
};
int spfa(int sx,int sy)
{
queue<Gar> q;
bool visit[1001][1001]={false}; //数组判断当前松弛节点是否在队列中
const int dx[3]={0,-1,-1},dy[3]={0,0,-1}; //事先存好题目中的两大方向
q.push(Gar(sx,sy));
step[sx][sy]=pictrue[sx][sy];
while(!q.empty())
{
Gar u=q.front();
q.pop(); //取出节点尝试通过它来松弛
if(u.x==R)
{
ans=max(ans,step[u.x][u.y]); //如果到底了,存起最优答案
continue;
}
visit[u.x][u.y]=true;
for(int i=1;i<=2;i++)
{
int nx=u.x-dx[i],ny=u.y-dy[i];
if(nx<=0||nx>R||ny<=0||ny>R) continue; //判断是否超界
if(step[nx][ny]<step[u.x][u.y]+pictrue[nx][ny]) //判断是否可以松弛
{
step[nx][ny]=step[u.x][u.y]+pictrue[nx][ny]; //STEP就是前文所述的DIS
if(!visit[nx][ny])
{
q.push(Gar(nx,ny)); //如果不在队列就放入队列
visit[nx][ny]=true;
}
}
}
}
}
int main()
{
scanf("%d",&R);
for(int i=1;i<=R;i++)
for(int j=1;j<=i;j++)
{
cin>>a;
pictrue[i][j]=a;
}
memset(step,-1,sizeof(step)); //由于是最大路,因此数组初始为负数
ans=-1;
spfa(1,1);
printf("%d",ans);
return 0;
}
总结:
不难看出,由于为了 d i s dis dis 数组的最优性,一个图每个节点往往不止要访问一次,这就导致了SPFA在遇见稠密图的时候对时间的需求过高,很容易被卡掉 (所以他死了),在题目允许的情况下应该多避免使用 SPFA,毕竟,大多数最短路径问题都是可以用时间效率更高的 D i j k s t r a Dijkstra Dijkstra 来做。
总结的最后:
看到这里,或许有人会吐槽我,为什么一到这么水的题,有简短的DP不用,非得要浪费生命的打一个这么长的最短路代码。您当然可以这么觉得,但是我的博客只是为了更多的C++猿友看到算法的魅力,知道题目不仅仅有固定的配偶,让广大的C++爱好者发现原来算法和题目还能这么串联起来,那么我就不枉打这么多字来写这篇博客了。
能在鄙人这里得到一丁点启发的朋友们,欢迎留下你们的支持!!:)
如果你喜欢我的内容,那么也请支持一下他吧
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!