题目链接:D. Flights for Regular Customers
题目大意:给定一个(n)个点(m)条边的有向图,第(i)条边只有再你之前经过了(d_i)条边之后才可以通过,求从(1)点到(n)号点的最短距离。
题解:因为边的(d_i)限制很麻烦,所以先考虑除掉这个限制,可以将所有边按照(d_i)排序后依次加入到原图中去,这样就去掉了这个限制。
那么处理答案可以先找出所有用(d_i)步可以到达的点,然后对整张图跑一边 bfs 求出最小的答案。
那么怎么找出(d_i)步可以到达的点的个数呢?可以将边集用邻接矩阵来处理,然后跑一遍矩阵快速幂即可。好了,这一题做完了。
算一下时间复杂度是(O(n^3m ext{log}d))的,这个数据范围虽然小,不过应该过不掉(如果卡常过掉的请受我一拜),可是这个算法怎么优化呢?似乎没有办法优化了,这是我们想起一句极为经典话:“智商不够,压位来凑。”注意到转移矩阵只有(01)两个值,所以可以用 bitset 来优化,那么邻接矩阵就需要反过来,即原来(u-->v)的边要反向变为(v-->u)的边,否则再快速幂时很难优化。时间复杂度为(O(frac{n^3m ext{log}d}{omega}))。
下面是代码:
#include <queue>
#include <bitset>
#include <cstdio>
#include <algorithm>
using namespace std;
const int Maxn=150;
const int Maxm=150;
const int Inf=0x3f3f3f3f;
int n,m;
struct Edge{
int u,v;
int d;
friend bool operator <(Edge p,Edge q){
return p.d<q.d;
}
}edge[Maxm+5];
int dis[Maxn+5];
struct Matrix{
bitset<Maxn+5> a[Maxn+5];
friend bitset<Maxn+5> operator *(bitset<Maxn+5> a,Matrix b){
bitset<Maxn+5> ans;
for(int i=0;i<n;i++){
ans[i]=(a&b.a[i]).any();
}
return ans;
}
friend Matrix operator *(Matrix a,Matrix b){
Matrix ans;
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
if(a.a[i][j]){
ans.a[i]=ans.a[i]|b.a[j];
}
}
}
return ans;
}
}a;
void quick_power(Matrix a,int b,bitset<Maxn+5> &ans){
while(b){
if(b&1){
ans=ans*a;
}
a=a*a;
b>>=1;
}
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
scanf("%d%d%d",&edge[i].u,&edge[i].v,&edge[i].d);
edge[i].u--;
edge[i].v--;
}
sort(edge+1,edge+1+m);
int last=0;
bitset<Maxn+5> vis;
vis[0]=1;
int ans=Inf;
for(int i=1;i<=m;i++){
if(edge[i].d>=ans){
break;
}
quick_power(a,edge[i].d-last,vis);
last=edge[i].d;
queue<int> q;
a.a[edge[i].v][edge[i].u]=1;
for(int j=0;j<n;j++){
if(vis[j]){
dis[j]=0;
q.push(j);
}
else{
dis[j]=Inf;
}
}
while(!q.empty()){
int u=q.front();
q.pop();
for(int v=0;v<n;v++){
if(a.a[v][u]){
if(dis[v]==Inf){
dis[v]=dis[u]+1;
q.push(v);
}
}
}
}
ans=min(ans,dis[n-1]+edge[i].d);
}
if(ans==Inf){
puts("Impossible");
}
else{
printf("%d
",ans);
}
return 0;
}