现在的位置: 首页 > 综合 > 正文

poj 3613

2017年04月23日 ⁄ 综合 ⁄ 共 2467字 ⁄ 字号 评论关闭

转载:http://www.cnblogs.com/Missa/archive/2012/09/03/2669746.html

题意:

求从一个点s 到 一点 e 经过 n 条边的最短路经是多少(可以有重边)

贴一个floyd算法讲解:

http://blog.csdn.net/niushuai666/article/details/6772706

以前一直没仔细想过floyd算法,觉得很简单,今天做这题的时候,看网上的报告都有一句:floyd是每次使用一个中间点k去更新i,j之间的距离,那么更新成功表示i,j之间恰有一个点k时的最短路,如果做N - 1次floyd那么不就是i,j之间借助N - 1 个点时的最短路了。看了很久不明白为什么。也对floyd的最外围的那个k<n产生了疑惑。后来突然想到了,floyd每次更新的都是本身,假如k=3时,dis[1][5]借助dis[1][3]和dis[3][5]成为最短路。不需要去知道1,3之间有多少个点,因为前面已经求出,,(dp?)只知道这一次求出的dis[1][5]多加了一个3这个点。

回到这题,floyd算法是对自身矩阵更新,而这道题却是更新到另一个矩阵上,所以不会出现刚更新过的值又来更新。。例如下面代码的b.mat[1][3] c.mat[3][5]就分别代表上面的dis[1][3],dis[3][5].我们不需要知道tmp.mat[1][5]已经有的点个数。(即已经更新的次数。)只知道,这次更新会加入一个3到他们中间。所以更新k-1次就行。




这个题目我们可以用一个矩阵f[i][j][n]表示进过的边数为n的i到j的最短路,利用二分的思想就可以得到f[i][j][n]=min{f[i][k][n/2]+f[k][j][n-n/2]},这样就可以用快速幂去做了。只要先求得f[i][k][n/2],就可以利用f[i][k][n/2]求得f[k][j][n-n/2],于是一共只需要计算O(logn)个矩阵,每个矩阵是2维的,计算的时候可以用floyd。

void floyd(Mat b,Mat c)
{
    tmp.init();
    for(int k=1;k<=cnt;k++)
        for(int i=1;i<=cnt;i++)
            for(int j=1;j<=cnt;j++)
            {    
                if(b.mat[i][k] <0 || c.mat[k][j] <0)
                    continue;
                if(tmp.mat[i][j]<0 || tmp.mat[i][j] > b.mat[i][k] + c.mat[k][j]  )
                     tmp.mat[i][j] = b.mat[i][k] + c.mat[k][j];
            }
}

注意的地方:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

#define maxn 205

int n,t,st,en;
struct Mat
{  
    int mat[maxn][maxn];  
    void init()
    {
        memset(mat,-1,sizeof(mat));
    }
};  

int v[1005];//离散化用
Mat ans,tmp,map;
int cnt;//顶点个数
void init()
{
    int val,s,e;
    memset(v,0,sizeof(v));
    cnt=0;
    ans.init();
    tmp.init();
    map.init();
    for(int i=0;i<maxn;i++)
        ans.mat[i][i]=0;
    for(int i=0;i<t;i++)
    {
        scanf("%d%d%d",&val,&s,&e);
        if(v[s]==0)
        {
            ++cnt;
            v[s]=cnt;
        }
        if(v[e]==0)
        {
            ++cnt;
            v[e]=cnt;
        }
        if(map.mat[v[s]][v[e]]<0 || map.mat[v[s]][v[e]] > val)
            map.mat[v[s]][v[e]]=map.mat[v[e]][v[s]]=val;
    }
}

void floyd(Mat b,Mat c)
{
    tmp.init();
    for(int k=1;k<=cnt;k++)
        for(int i=1;i<=cnt;i++)
            for(int j=1;j<=cnt;j++)
            {    
                if(b.mat[i][k] <0 || c.mat[k][j] <0)//意味着是inf
                    continue;
                if(tmp.mat[i][j]<0 || tmp.mat[i][j] > b.mat[i][k] + c.mat[k][j]  )
                     tmp.mat[i][j] = b.mat[i][k] + c.mat[k][j];
            }
}

Mat copy()
{
    Mat a;
    for(int i=0;i<=cnt;i++)
        for(int j=0;j<=cnt;j++)
            a.mat[i][j]=tmp.mat[i][j];
    return a;
}

void solve()
{
    while(n)//虽然这里是n,像是更新了n次。但是下面80行那里第一次调用的时候其实并未更新。只是把map,传递给tmp再转给ans.
    {
        if(n&1)
        {
            floyd(ans,map);
            ans=copy();
        }
        floyd(map,map);
        map=copy();
        n>>=1;
    }
}

int main()
{
    while(scanf("%d%d%d%d",&n,&t,&st,&en) != EOF)
    {
        init();
        solve();
        printf("%d\n",ans.mat[v[st]][v[en]]);
    }
    return 0;
}

1、N次floyd可以用倍增思想加速,就是自底向上的二分。类似求矩阵快速幂(M67大牛)。

2、这里还有一点要注意,T的范围是(2~100),所以最多顶点只有200,而顶点标号的范围却(1 ≤ I1i ≤ 1,000; 1 ≤ I2i ≤ 1,000)。这样我们可以将编号离散化。

3、最后一点这个题的inf要开很大。开始开的是0x5fffffff还是wa了。后来看了某个博客 可以把inf定义为-1。就没这个问题了。值得学习。

ps:这道题还是有点似懂非懂.....

抱歉!评论已关闭.