题目大意:求从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;
}