POJ 3613 Cow Relays Floyd+快速幂加速

题目大意:求从S到T 恰好经过k条边的最短路。(2<=k<=1000000)

解题思路:最开始看到题目以为是K短路,不过看到K的数量级就明显不适合了。

根据题意,既有最短路,k这么大,时间限制还是1s,便想到去优化。

然后看到点数为1000,而边数最多只有100,先进行一步离散化,不过复杂度还是不够,然后就开始考虑这道题的特殊性了。


K最多为1000,000,而边数只有一百,可以预见中间有许多状态是重复出现的,甚至有可能在一个环中不停地走。

于是考虑把中间重复的计算给优化一下。便想到了快速幂优化和ST算法。(这两个都是能基于重复的状态进行优化)

而便于重复状态的记录的话,用矩阵来存储状态是再合适不过的了,由此想到最短路用Floyd来求。


先简述一下快速幂优化的思想:

如果想求K^a, 假设a=43.  我们把a转化为二进制便是 101011

接下来表示时我们把指数都用二进制表示,原式便是求 K^(101011) ,

=K^(1<<0) + K^(1<<1) + K^(1<<3) +K^(1<<5) , 

而K^(1<<1)= (K^(1<<0))^2 , K^(1<<2)= (K^(1<<1))^2 , K^(1<<3)= (K^(1<<2))^2 ,K^(1<<4)= (K^(1<<3))^2 ,K^(1<<5)= (K^(1<<4))^2 ....

也就是说每一位都可以由上一位的平方得到,原来的指数级运算便成了加法运算..

而只要是1e9级以内的指数运算我们都可以在32次运算内完成....( 很强大... ) (  当然...不要给我说数据溢出什么的...那种题一般要取余,取余方法自己找快速幂看 )


再回到这题,用Floyd算法的话,

假设从a到b经过恰好经过 (1<<i) 条路的最短路是len[a][b][ 1<<i ]=min(len[ a ][ c ][ 1<<(i-1) ] + len(len[ c ][ b ][ 1<<(i-1) ]

哪怕是经过101011条路径的最短路我们也都可以像上面的优化一样依次求出。

具体实现看代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define FOR(i,l,r) for(int i=(l);i<=(r);++i)
#define REP(i,n) for(int i=0;i<(n);++i)
#define N 1010
#define INF 1e9
#define min(a,b) (a<b?a:b)

int map[2][N][N];
int ans[2][N][N];
int f[N];
bool flag[N];
void floyd(int num,int cur,int pre)
{
    REP(i,num)
        REP(j,num)
            map[cur][f[i]][f[j]]=INF;
    REP(k,num)
        REP(i,num)
            REP(j,num)
                map[cur][f[i]][f[j]]=min(map[cur][f[i]][f[j]],map[pre][f[i]][f[k]]+map[pre][f[k]][f[j]]);
}

int main()
{
    int n,m,start,end;
    int u,v,c;
    while(cin>>n>>m>>start>>end)
    {
        memset(flag,0,sizeof(flag));
        FOR(i,1,1000)
            FOR(j,1,1000)
                map[0][i][j]=INF; //初始化
        int num=0;
        while(m--)
        {
            scanf("%d%d%d",&c,&u,&v);
            map[0][u][v]=map[0][v][u]=c;
            if(!flag[u]) f[num++]=u,flag[u]=1; //离散化
            if(!flag[v]) f[num++]=v,flag[v]=1; //离散化
        }
        REP(i,num)
        {
            REP(j,num)
            ans[0][f[i]][f[j]]=INF;
            ans[0][f[i]][f[i]]=0;   //初始化
        }
        int cur=0,pre=1;
        int temp1=0,temp2=1;   
        //temp1是当前状态,temp2是之前的状态
        //因为map[1<<i][a][b] 只与 map[1<<(i-1)][x][y] 有关
        //所以这两个数组能循环利用
        //没必要开32位,开两位足以,是一种空间上的优化
        while(n)
        {
            temp1^=1,temp2^=1;
            floyd(num,temp1,temp2);//i从0开始,在进行第i次while()循环时,求出从a到b恰好经过(1<<i)条边的最短路是多少
            if(n&1) //如果当前位为1才进行计算,把ans进行更新
            {
                cur^=1,pre^=1;//和temp1,temp2的效果是一样的
                REP(i,num)
                    REP(j,num)
                        ans[cur][f[i]][f[j]]=INF;//只用对离散化后的点初始化
                REP(k,num)
                    REP(i,num)
                        REP(j,num)
                            ans[cur][f[i]][f[j]]=min(ans[cur][f[i]][f[j]],ans[pre][f[i]][f[k]]+map[temp2][f[k]][f[j]]);
                            //在离散化后的点中进行Floyd
            }
            n>>=1;
        }
        cout<<ans[cur][start][end]<<endl;
    }
    return 0;
}





  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值