图论:P1613跑路 最短路径 倍增 Floyd
P1613跑路
题目传送门:P1613 跑路 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
题目:
思路:
可以考虑倍增的思想,用一个四层循环来计算两个点的路径是不是二的k次方倍,具体思路就是第一层循环循环2的次方,从1循环到64,因为题目说这个机器是用longint存储的,所以最多只能跑2^64,所以只要循环到64就行了,一定要从1->64,从小到大,只有算出来小的,才能算出来大的,算是一个递推的思想,中间两层循环循环区间的端点,也就是起点和终点,在此之前我们要构建一个bool can[i][j][k]数组,代表的是起点为i,终点为j,路径是否为2^k次方,true代表可以,false代表不可以。所以最内层循环就是要循环间断点,x,如果can[i][x][k-1]为true并且can[x][j][k-1]为true,则can[i][j][k]为true,并且还要构建一个dis[i][j]数组,代表i到j的时间,所以dis[i][j]就要改成1.因为每两个k-1次方的区间合并就是k次方,这个思路把图转换为线性的区间DP的样式。然后我们算出所有的dis[i][j],也就是每两个点之间的边权以后,用floyd算法计算最短路径即可,注意要把没有边得两个点的dis初始化为无穷大,便于floyd。
倍增算法:
1 for(int m=1;m<=n;++m) 2 for(int i=1;i<=n;++i) 3 for(int j=1;j<=n;++j) 4 for(int k=1;k<=n;++k) 5 { 6 if(can[i][k][m-1]&&can[k][j][m-1])//因为两个路径如果都是2^m-1 合在一起就是2^m 7 { 8 dis[i][j]=1; 9 can[i][j][m]=1; 10 } 11 }
Floyd
//floyd for(int k=1;k<=n;++k) for(int j=1;j<=n;++j) for(int i=1;i<=n;++i) { if(dis[i][j]>dis[i][k]+dis[k][j]) { dis[i][j]=dis[i][k]+dis[k][j]; } }
最后的ans就是dp[1][n]了。
完整AC代码:
1 #include<iostream> 2 #include<algorithm> 3 #include<cstring> 4 using namespace std; 5 const int maxn=55; 6 bool can[maxn][maxn][65];//代表点i和点j之间是否有一条距离为2的k次方的路径 7 int dis[maxn][maxn];//代表 i 和 j之间的最短路径 8 int n,m,a,b; 9 int main() 10 { 11 cin>>n>>m; 12 memset(dis,0x3f,sizeof(dis));//求最短路径 将路径初始化为很大的值 13 for(int i=1;i<=m;++i) 14 { 15 cin>>a>>b; 16 dis[a][b]=1;//2^0=1 17 can[a][b][0]=1; 18 } 19 for(int m=1;m<=n;++m) 20 for(int i=1;i<=n;++i) 21 for(int j=1;j<=n;++j) 22 for(int k=1;k<=n;++k) 23 { 24 if(can[i][k][m-1]&&can[k][j][m-1])//因为两个路径如果都是2^m-1 合在一起就是2^m 25 { 26 dis[i][j]=1; 27 can[i][j][m]=1; 28 } 29 } 30 31 //floyd 32 for(int k=1;k<=n;++k) 33 for(int j=1;j<=n;++j) 34 for(int i=1;i<=n;++i) 35 { 36 if(dis[i][j]>dis[i][k]+dis[k][j]) 37 { 38 dis[i][j]=dis[i][k]+dis[k][j]; 39 } 40 } 41 cout<<dis[1][n]; 42 return 0; 43 }