题目链接
给出一张有向图,对于边 i 有限制条件 di ,表示在走边 i 前必须走过至少 di 条其他的边。为从点 1 到点 n 最少要走几条边。
比较容易想到的暴力是 fi,j 表示在走 j 步能不能正好到 i 点。那么 fi,j 可以转移到 fk,j+1 , k 为与 i 间有边 e 且 de≤j 。
这种做法在 di 很小的时候才可以通过。所以说貌似没什么用。
考虑以前DNA Sequence这类题目用到过的思路,邻接矩阵的 k 次方等于 i 正好走 k 步到 j 的方案数。考虑边的限制条件,如果我们现在已经走过了 k 步,那么我们能用的边只有 di≤k 的所有的边。我们就可以把边按照 di 升序排序,将 di 按顺序加入边集,分段通过矩阵快速幂转移。维护点与点之间走 k 步的到达情况。注意这里我们只需要知道能不能走到而并不需要知道方案数,那么我们的矩阵就永远是01矩阵。用朴素的矩阵乘法在这题会T掉,所以用bitset加速。加速后矩阵乘法如下:
void mul(bitset *a, bitset *b){
bitset ret[N];
rep(i, 1, n) rep(j, 1, n)
if(a[i][j]) ret[i] |= b[j];
rep(i, 1, n) a[i] = ret[i];
}
bitset a[N], b[N];
mul(a, b);
思路是,如果 i 能走到 j ,那么 j 能走到的所有点 i 都能走到。
在枚举边的时候通过 Floyd 维护最短路。如果在某个时刻走了 k 步, 1 可以到达 j 且 j 到 n 有最短路。那么就可以更新答案。
#include
using namespace std;
using std::bitset;
#define rep(i, l, r) for(register int i = (l); i <= (r); i++)
const int N = 150 + 10, inf = 0x7fffffff;
int n, m, ans;
int d[N][N];
struct Edge{
int u, v, w;
bool operator < (const Edge &rhs) const{
return w < rhs.w;
}
}edg[N];
bitset ari[N], g[N];
void mul(bitset *a, bitset *b){
bitset ret[N];
rep(i, 1, n) rep(j, 1, n)
if(a[i][j]) ret[i] |= b[j];
rep(i, 1, n) a[i] = ret[i];
}
void poww(bitset *a, int k){
bitset ret[N];
rep(i, 1, n) ret[i][i] = 1;
while(k){
if(k & 1) mul(ret, a);
mul(a, a);
k >>= 1;
}
rep(i, 1, n) a[i] = ret[i];
}
void work(){
bitset tmp[N];
int now = 0; ans = inf;
memset(d, 0x3f, sizeof(d));
for(int i = 1; i <= n; i++) d[i][i] = 0, ari[i][i] = 1;
rep(i, 1, m){
int u = edg[i].u, v = edg[i].v, w = edg[i].w;
rep(j, 1, n) rep(k, 1, n)
d[j][k] = min(d[j][k], d[j][u] + 1 + d[v][k]);
rep(j, 1, n) tmp[j] = g[j];
poww(tmp, w - now);
mul(ari, tmp);
rep(j, 1, n)
if(ari[1][j] && d[j][n] != 1061109567){
ans = min(ans, w + d[j][n]);
}
now = w;
g[u][v] = 1;
}
if(ans == inf) puts("Impossible");
else printf("%d\n", ans);
}
void init(){
scanf("%d%d", &n, &m);
for(int i = 1; i <= m; i++)
scanf("%d%d%d", &edg[i].u, &edg[i].v, &edg[i].w);
sort(edg + 1, edg + m + 1);
}
int main(){
init();
work();
return 0;
}