图论基础

本部分包含的内容有:最短路,拓扑排序,最小生成树,树的直径和最近公共祖先,图的连通性等。

P1266速度限制

不难看出,这道题除了“有些道路没有速度限制”,就是一个裸的最短路。

我们可以用分层图的思想,将速度 v 看做单独的一维,另 dis[i][j] 表示从起点到点 i ,并且当前速度为 j 时的最短路。

于是 Dij 的状态转移方程就是:

当前边有速度限制时: dis[ver[i]][v(fir.f,ver[i])]=dis[fir.f][fir.v]+len(fir.f,ver[i])/v(fir.f,ver[i]) ;

当前边没有速度限制时:dis[ver[i]][fir.v]=dis[fir.f][fir.v]+len(fir.f,ver[i])/fir.v

code:

void print(int x,int y){
	if(!x){
		printf("0 ");
		return ;
	}
	print(pre[x][y],pre2[x][y]);
	printf("%d ",x);
	return ;
}
int main(){
	scanf("%d%d%d",&n,&m,&d);
	for(int i=1;i<=m;++i){
		scanf("%d%d%lf%lf",&a,&b,&v,&l);
		if(a==0&&!v)
			v=70;
		add(a,b,v,l);
	}
	for(int i=1;i<n;++i)
		for(int j=0;j<=500;++j)
			dis[i][j]=1e9;
	q.push((node){0,0,0});
	while(!q.empty()){
		node fir=q.top();q.pop();
		if(vis[fir.f][fir.v])
			continue;
		vis[fir.f][fir.v]=1;
		for(int i=head[fir.f];i;i=nxt[i]){
			if(wv[i]&&dis[ver[i]][(int)wv[i]]>dis[fir.f][fir.v]+wl[i]/wv[i]){
				dis[ver[i]][(int)wv[i]]=dis[fir.f][fir.v]+wl[i]/wv[i];
				if(!vis[ver[i]][(int)wv[i]]){
					q.push((node){ver[i],wv[i],dis[ver[i]][(int)wv[i]]}),
					pre[ver[i]][(int)wv[i]]=fir.f;
					pre2[ver[i]][(int)wv[i]]=fir.v;
				}
			}
			else if(!wv[i]&&dis[ver[i]][fir.v]>dis[fir.f][fir.v]+wl[i]/fir.v){
				dis[ver[i]][fir.v]=dis[fir.f][fir.v]+wl[i]/fir.v;
				if(!vis[ver[i]][fir.v]){
					q.push((node){ver[i],fir.v,dis[ver[i]][fir.v]}),
					pre[ver[i]][fir.v]=fir.f;
					pre2[ver[i]][fir.v]=fir.v;
				}
			}
		}
	}
	dis[x=0][y=0]=1e9;
	for(int i=1;i<=500;++i)
		if(dis[d][i]<dis[x][y])
			x=d,y=i;
	//cout<<dis[x][y]<<endl;
	print(x,y);
	return 0;
}

P2296 [NOIP2014 提高组] 寻找道路

因为起点可以是 1N 中的任意一个,而终点只有一个,所以可以建反向图, BFS 找出所有符合题意的点。然后在所有符合题意的点中跑最短路即可。

code:

int main(){
	cin>>n>>m;
	for(int i=1;i<=m;++i){
		cin>>x>>y;
		e1[x].push_back(y);//正向边 
		e2[y].push_back(x);//反向边 
	}cin>>fir>>end;
	queue <int>q;
	q.push(end);t[end]=1; 
	while(!q.empty()){
		int xy=q.front();q.pop();
		for(int i=e2[xy].size()-1;i>=0;--i)
			if(t[e2[xy][i]]==0)
				q.push(e2[xy][i]),
				t[e2[xy][i]]=1;//从终点往前推,找出能够到终点的点 
	}
	if(t[fir]==0)
		{ cout<<"-1"<<endl;return 0; }
	for(int i=1;i<=n;++i)
		if(t[i]==1){
			t2[i]=1;
			for(int j=e1[i].size()-1;j>=0;--j)
				if(t[e1[i][j]]==0)
					{t2[i]=0;break;}
		}//判断该点的所有出边是否都能到终点 
	if(t2[fir]==0)
		{ cout<<"-1"<<endl;return 0; }
	q.push(fir);f[fir]=1;
	while(!q.empty()){//从最终选出的点中找最短路径 
		int z=q.front();q.pop();
		if(z==end){
			cout<<f[z]-1<<endl;
			return 0;//由广搜性质可知最先搜到路径一定最短 
		}
		for(int i=e1[z].size()-1;i>=0;--i){
			int next=e1[z][i];
			if(t2[next]==1&&f[next]==0){//注意要有f[next]==0的特判,否则会超时 
				q.push(next);
				f[next]=f[z]+1;
			}
		}
	}cout<<"-1"<<endl;
	return 0;
}

P3243 [HNOI2015] 菜肴制作

先说结论:建反图,然后在反图上用大根堆拓扑排序。

证明来自讨论区:

题目要求1、2、3依次尽量靠前

我们发现这种反向方法最后一个数一定是可能的最大值,倒数第二个数一定是在此条件下除去最后一个的最大值,以此类推

所以在这种方法中,我们会尽量早使用尽可能大的序号,尽量晚使用1、2、3等较小序号

也即1尽量靠前、在此标准下2尽量靠前... ...

code:

void f(){
	for(int i=1;i<=n;++i)
		if(in[i]==0)
			q.push(i);
	while(!q.empty()){
		int s=q.top();q.pop();
		ans[++tot]=s;
		for(int i=0;i<p[s].size();++i){
			--in[p[s][i]];
			if(in[p[s][i]]==0)
				q.push(p[s][i]);
		}
	}
}
void _clear(){
	for(int i=1;i<=n;++i)
		p[i].clear(),in[i]=0;
	tot=0;
}
int main(){
	cin>>t;
	while(t --> 0){
		_clear();
		cin>>n>>m;
		for(int i=1;i<=m;++i){
			cin>>x>>y;
			p[y].push_back(x);
			++in[x];
		}
		f();
		if(tot<n)
			cout<<"Impossible!";
		else
			for(int i=tot;i>=1;--i)
				cout<<ans[i]<<" ";
		cout<<endl;
	}
	return 0;
}

P1613 跑路

因为每次可以跑 2k(kN) 条边,所以我们可以判断一下对于任意两个点,是否存在一条长为 2n(n[0,k]) 的路径,只要存在,就把它们连起来,这样就形成了一个新图。然后在这个图上跑最短路即可。

int main(){
	scanf("%lld%lld",&n,&m);
	ans=1e9;
	for(int i=1;i<=m;++i){
		scanf("%lld%lld",&u,&v);
		e[u][v][0]=1;
	}
	for(int l=1;l<=31;++l)
		for(int k=1;k<=n;++k)
			for(int i=1;i<=n;++i)
				for(int j=1;j<=n;++j)
					e[i][j][l]=e[i][j][l]|(e[i][k][l-1]&e[k][j][l-1]);
    for(int l=0;l<=31;++l)
    	for(int i=1;i<=n;++i)
	    	for(int j=1;j<=n;++j)
		    	if(i!=j){
                    if(e[i][j][l]) g[i][j]=1;
                    else if(g[i][j]==0)
                        g[i][j]=1e9;
                }
	for(int k=1;k<=n;++k)
		for(int i=1;i<=n;++i)
			for(int j=1;j<=n;++j)
				g[i][j]=min(g[i][j],g[i][k]+g[k][j]);
	printf("%lld\n",g[1][n]);
	return 0;
}
posted @   andy_lz  阅读(18)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示