Codeforces 576D - Flights for Regular Customers(bitset 优化广义矩阵乘法)
题意:
有一张 \(n\) 个点 \(m\) 条边的有向图,你初始在 \(1\) 号点,边上有边权 \(c_i\) 表示只有当你经过至少 \(c_i\) 条边的时候你才能经过第 \(i\) 条边。
求从 \(1\) 号点开始最少走过多少条边才能到达 \(n\) 号点。
\(n,m \leq 150,c_i\leq 10^9\)
注意到题目中 \(c_i\) 的数据范围可以达到 \(10^9\),我们显然不能一步步枚举可达的位置。
但是 \(m\) 的数据范围很小,说明转移矩阵最多改变 \(150\) 次,这启示我们可以用矩阵乘法
具体来说,我们建立 \(1\times n\) 的矩阵 \(A\),\(A_i\) 表示在当前状态下 \(i\) 是否能够到达(\(0/1\))。
再建立一个 \(n\times n\) 的转移矩阵 \(B\),\(B_{i,j}\) 表示 \(i\) 与 \(j\) 是否有边相连。
那么走一步之后,新的 \(A'_i=\or_{i=1}^nA_k\and B_{k,i}\)
也就是说,如果存在一个点 \(k\) 使得在当前状态下能够到达 \(k\) 并且 \(k\) 与 \(i\) 有直接边相连,那么就可以从 \(k\) 走到 \(i\)。
由于广义矩阵乘法也满足乘法结合律,那么走 \(t\) 步后可达状态为 \(A\times B^t\)
考虑将边按照 \(c_i\) 从小到大排序,然后依次改变转移矩阵 \(B\)。
然后做一遍多源 \(bfs\),求出可达的所有点中,到达点 \(n\) 的最短距离。
时间复杂度 \(\mathcal O(n^3m\log c_i)\)。注意到矩阵 \(A,B\) 每一位都是 \(0\) 或 \(1\),矩阵乘法中也只包含位运算,因此可以使用 bitset 优化,时间复杂度 \(\mathcal O(\frac{n^3m\log c_i}{\omega})\) 。
#include <bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define fz(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
#define ffe(it,v) for(__typeof(v.begin()) it=v.begin();it!=v.end();it++)
#define fill0(a) memset(a,0,sizeof(a))
#define fill1(a) memset(a,-1,sizeof(a))
#define fillbig(a) memset(a,63,sizeof(a))
#define pb push_back
#define mp make_pair
typedef pair<int,int> pii;
typedef long long ll;
const int MAXN=150+5;
const int MAXM=150+5;
int n,m,d[MAXN];
struct edge{
int u,v,c;
friend bool operator <(edge a,edge b){
return a.c<b.c;
}
} e[MAXM];
struct mat{
bitset<MAXN> x[MAXN];
friend mat operator *(mat a,mat b){
mat c;
for(int i=1;i<=n;i++) for(int j=1;j<=n;j++)
if(a.x[i][j]) c.x[i]|=b.x[j];//optimize multiplation by bitset
return c;
}
} to,c;
mat qpow(mat x,int e){
mat ret;for(int i=1;i<=n;i++) ret.x[i][i]=1;
while(e){if(e&1) ret=ret*x;x=x*x;e>>=1;}return ret;
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++) scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].c);
sort(e+1,e+m+1);to.x[1][1]=1;int pre=0,ans=2e9;
for(int i=1;i<=m;i++){
to=to*qpow(c,e[i].c-pre);
pre=e[i].c;c.x[e[i].u][e[i].v]=1;
memset(d,63,sizeof(d));
queue<int> q;for(int j=1;j<=n;j++) if(to.x[1][j]) q.push(j),d[j]=0;
while(!q.empty()){
int cur=q.front();q.pop();
for(int j=1;j<=n;j++) if(c.x[cur][j]&&d[j]>1e9){
d[j]=d[cur]+1;q.push(j);
}
}
if(d[n]<1e9) ans=min(ans,e[i].c+d[n]);
}
if(ans==2e9) puts("Impossible");
else printf("%d\n",ans);
return 0;
}