题目大意
给定一个图,有T( T<=100 )条无向边,求从S到E恰经N条边的最短路径。点数tot不大于1000。题解
没想到矩阵乘法加快速幂还有这种用处。这种解法本质上是dp,用f[k][i][j]表示恰经过k条边时i到j的最短路,则有:
f(k,i,j)=min(f(k−p,i,temp)+f(p,temp,j))
但暴力显然要T,所以使用倍增优化。用F[k][i][j]表示恰经 2k 条边时i到j的最短路,则有:
F(k,i,j)=min(f(k−1,i,temp)+f(k−1,temp,j)
i到j经过 <=2k <script type="math/tex" id="MathJax-Element-117"><=2^k</script>条边的最短路g[k][i][j]是:
g(k,i,j)=min(g(k−1,i,j),f(k,i,j))
那么i到j经过N条边的最短路可以把N进行二进制分解,然后用矩阵乘法加快速幂解决。这里矩阵加法应替换为求最小值,矩阵乘法替换为求加法。时间复杂度 O(T3log2N) ,符合要求。Code:
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn = 105, maxp = 1005, oo = -1;
int N, T, S, E;
int h[maxp], sum;
struct matrix {
int data[maxn][maxn];
int n;
matrix(int n = 0) :n(n) { memset(data, oo, sizeof(data)); }
matrix operator * (matrix &b) {
matrix c = matrix(n);
for(int i = 1; i <= n; ++i) for(int j = 1; j <= n; ++j)
for(int k = 1; k <= n; ++k) {
if(data[i][k] == oo || b.data[k][j] == oo) continue;
if(c.data[i][j] == oo || c.data[i][j] > data[i][k] + b.data[k][j])
c.data[i][j] = data[i][k] + b.data[k][j];
}
return c;
}
matrix operator ^ (int k) {
matrix t = (*this);
while(k != 0) {
if(k & 1) (*this) = (*this)*t;
k >>= 1;
t = t*t;
}
return (*this);
}
}g;
void read(int &a) {
int f = 1;
char c = getchar();
a = 0;
while(c < '0' || c > '9') {
if(c == '-') f = -1;
c = getchar();
}
while(c >= '0' && c <= '9') {
a = a*10 + c - 48;
c = getchar();
}
a *= f;
}
void write(int a) {
int t = 0;
char c[30];
if(a < 0) { putchar('-'); a = -a; }
do {
c[t++] = a%10 + 48;
a /= 10;
} while(a);
while(t--) putchar(c[t]);
putchar('\n');
}
void init() {
int s, t, l, maxm = 0;
read(N); read(T); read(S); read(E);
memset(h, oo, sizeof(h));
g = matrix(100);
for(int i = 1; i <= T; ++i) {
read(l); read(s); read(t);
if(h[s] == oo) h[s] = ++sum;
if(h[t] == oo) h[t] = ++sum;
g.data[h[s]][h[t]] = g.data[h[t]][h[s]] = l;
}
}
void work() {
g = (g^(N-1));//上面从1次方开始乘,所以这里N-1
write(g.data[h[S]][h[E]]);
}
int main() {
init();
work();
return 0;
}