返回顶部

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;
}
posted @ 2021-11-12 17:01  gyc#66ccff  阅读(39)  评论(0编辑  收藏  举报