2021.11.11-测试
昨天做了冯巨出的题\(\huge giao\)
\(\huge \color {#66ccff} {orz}\)
T1\(\color {darkred} 20\)
为什么要用矩阵
\(给定n个点m条边的有向图,只有走了d_i条边才能走第i条边,问最少走多少步\)
初始结构:
#include<bits/stdc++.h>
using namespace std;
const int inf=0x3f3f3f3f;
int n,m;
struct node{
int u,v,d;
bool operator < (const node &b){
return d<b.d;
}
}p[160];
const int N=160;
bitset<N> a[N],b[N];//01串a[i]表示含N个01的串
int f[N][N];
算法分析:
\(a[i][j]表示i到j有无一条路径\)
\(b[i][j]表示能否从i点走到j点\)
\(f[i][j]表示从i到j的路径长度\)
先让边按照\(d\)的值进行排序,从大到小依次枚举每一条边
然后进行操作:
当枚举到一条边时,距离解锁该条边还需要走\(\Delta d\)步,那么就考虑在这几步中,可以到达那些节点,以此来更新\(b\),每走一步,让\(a\)乘上一次自己,更新路径数量,再把\(a\)的幂与\(b\)相乘,来更新。
注意乘的过程中\(a\)不会变
即:如果\(i到j有路径,那么b[i][j]=1\)
然后用\(Floyed\)更新加入的边对于路径长度的影响
最后记录当前的\(d\),更新\(a\)
\(\huge \color {#39c5bb} {具体看代码}\)
初始化:
n=read(),m=read();
for(int i=1;i<=m;i++)
p[i].u=read(),p[i].v=read(),p[i].d=read();
sort(p+1,p+m+1);//按照d排序
memset(f,inf,sizeof(f));
for(int i=1;i<=n;i++)
f[i][i]=0;
int cnt=0;
int ans=inf;
for(int i=1;i<=n;i++)
b[i][i]=1;//自己可以到达自己
枚举边加上\(Floyed\)
for(int i=1;i<=m;i++){
int x=p[i].u,y=p[i].v,d=p[i].d;
int delta=d-cnt;//$\Delta d$
bitset<N> tmp[N];
for(int i=1;i<=n;i++)
tmp[i]=a[i];
fp(tmp,delta);//快速幂
Mul(b,tmp);
for(int j=1;j<=n;j++){
for(int k=1;k<=n;k++){
f[j][k]=min(f[j][k],f[j][x]+1+f[y][k]);
}
}
for(int j=1;j<=n;j++)
if(b[1][j])
ans=min(ans,d+f[j][n]);
a[x][y]=1;
cnt=d;
}
快速幂:
inline void Mul(bitset<N> *a,bitset<N> *b){
bitset<N> ret[N];
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(a[i][j])
ret[i]|=b[j];
for(int i=1;i<=n;i++)
a[i]=ret[i];
return ;
}
inline void fp(bitset<N> *a,int b){
bitset<N> ret[N];
for(int i=1;i<=n;i++)
ret[i][i]=1;
while(b!=0){
if(b&1) Mul(ret,a);
Mul(a,a);
b>>=1;
}
for(int i=1;i<=n;i++)
a[i]=ret[i];
return ;
}
总代码
\(\huge \color {#66ccff} {记得看注释}\)
#include<bits/stdc++.h>
#define inf 0x3f3f3f3f
using namespace std;
const int N=160;
int n,m;
struct node{
int u,v,d;
bool operator < (const node &b){
return d<b.d;
}
}p[N];
bitset<N> a[N],b[N];
int f[N][N];
//========================快读=======================
inline int read(){
int p=0,f=1;
char c=getchar();
while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
while(isdigit(c)){p=p*10+c-'0';c=getchar();}
return p*f;
}
//========================矩阵乘法===================
inline void Mul(bitset<N> *a,bitset<N> *b){//注意这里的矩阵乘法用或运算
bitset<N> ret[N];
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(a[i][j])//存在路径就将状态转移
ret[i]|=b[j];
for(int i=1;i<=n;i++)
a[i]=ret[i];
return ;
}
//=======================快速幂==========================
inline void fp(bitset<N> *a,int b){
bitset<N> ret[N];
for(int i=1;i<=n;i++)
ret[i][i]=1;
while(b!=0){
if(b&1) Mul(ret,a);
Mul(a,a);
b>>=1;
}
for(int i=1;i<=n;i++)
a[i]=ret[i];//把ret赋给a
return ;
}
//======================主函数===================
int main(){
n=read(),m=read();
for(int i=1;i<=m;i++)
p[i].u=read(),p[i].v=read(),p[i].d=read();
sort(p+1,p+m+1);//按照d排序
memset(f,inf,sizeof(f));
for(int i=1;i<=n;i++)
f[i][i]=0;
int cnt=0;
int ans=inf;
for(int i=1;i<=n;i++)
b[i][i]=1;//自己可以到达自己
for(int i=1;i<=m;i++){//枚举每一条边
int x=p[i].u,y=p[i].v,d=p[i].d;
int delta=d-cnt;
bitset<N> tmp[N];
for(int i=1;i<=n;i++)
tmp[i]=a[i];
fp(tmp,delta);//求a的幂
Mul(b,tmp);//让a与b相乘,更新矩阵b
for(int j=1;j<=n;j++){
for(int k=1;k<=n;k++){
f[j][k]=min(f[j][k],f[j][x]+1+f[y][k]);//Floyed
}
}
for(int j=1;j<=n;j++)
if(b[1][j])//可以走通就更新
ans=min(ans,d+f[j][n]);
a[x][y]=1;//记得更新a(因为此时已经走了)
cnt=d;
}
cout<<ans<<"\n";
return 0;
}