题意:一个无向图,求从一个顶点到另一个顶点的恰好经过k条边的最短路径,可能有自环,无重边。
分析:这个题其实就是矩阵乘法,用了dp的思想,dp[k][i][j]代表恰好经过k条边的i到j的最短路,那么dp[k][i][j]=min{dp[k-1][i][p]+g[p][j]}(其中g是原图)。可以用矩阵乘法来做,因为对于dp[k-1][i][j]来说,只要乘以一次原图矩阵,就相当于做了一次转移,得到的就是dp[k][][]矩阵。我想说这个题除了状态设计和floyd稍微沾了一点边之外,其他一点关系都没有了,竟然看到网上好多人用写floyd的循环顺序来写这个题,真是令人费解,这个题只是改了矩阵乘法的最内层,改变了矩阵乘法的运算规则,和floyd真的没关系啊!!!
另外,矩阵乘法的规则和在图中求一个点到另一个点的恰好经过k条边的方法数的规则是一样的,二者的状态转移方程也是一样的,所以矩阵乘法不需要经过任何修改就能做这种求恰好经过k条边的方法数的题,而不用像这个题还得做些修改。这些题都需要注意初始化的东西,尤其是g[i][i],就是定义一个点到本身的权值,本题需要定义为INF,因为如果题目不给自环边的话,那么本身到本身应该认为是不可达的,距离是无穷大的,千万不能定义为0;而求恰好经过k条边的方法数的题的话,应该初始化为0,因为该点到本身应该是有0种方法恰好经过一条边到达(如果没有自环,原图看做是恰好经过一条边从i到j的方法数)。一旦设置好了初始矩阵,神马存在自环啊或者其它环啊之类的问题都不需要我们考虑了,矩阵乘法都帮我们搞定。
这个题我还学到了个东西,就是求矩阵a的b次幂的刚刚进入函数部分的写法,我以前的写法都是把ans设置为单位矩阵,然后再进入循环做快速幂。今天突然看到有个人的代码,猛地反应过来可以直接设置ans等于传入的矩阵a,然后把幂次减一直接进入循环啊,不需要写设置单位矩阵部分的代码了,以前咋没怎么想呢。。。二了
/*
POJ 3613
code by heifrank
*/
#include <cstdio>
#include <map>
using namespace std;
const int MV = 205;
const int INF = 0x7fffffff;
int n;
map<int, int> M;
struct mat{
int ele[MV][MV];
mat(){init();}
void init(){
for(int i=0; i<MV; i++)for(int j=i; j<MV; j++)
ele[i][j] = ele[j][i] = INF;
}
mat operator*(const mat& ext){
mat ans;
for(int i=1; i<=n; i++)for(int j=1; j<=n; j++){
for(int k=1; k<=n; k++){
if(ele[i][k] == INF || ext.ele[k][j] == INF)continue;
ans.ele[i][j] = min(ans.ele[i][j], ele[i][k]+ext.ele[k][j]);
}
}
return ans;
}
}a;
mat get(mat a, int b){
mat ans = a;
b--;
while(b){
if(b&1)ans = ans*a;
a = a*a;
b >>= 1;
}
return ans;
}
int main(){
int T, m, st, ed, u, v, w;
scanf("%d%d%d%d", &T, &m, &st, &ed);
a.init();
for(int i=1; i<=m; i++){
scanf("%d%d%d", &w, &u, &v);
if(!M[u])M[u] = ++n;
if(!M[v])M[v] = ++n;
u = M[u], v = M[v];
a.ele[u][v] = a.ele[v][u] = min(a.ele[u][v], w);
}
mat ans = get(a, T);
printf("%d\n", ans.ele[M[st]][M[ed]]);
return 0;
}
Comments