Week7作业-最短路专题

Week7作业-最短路专题


TT 的魔法猫

众所周知,TT 有一只魔法猫。

这一天,TT 正在专心致志地玩《猫和老鼠》游戏,然而比赛还没开始,聪明的魔法猫便告诉了 TT 比赛的最终结果。TT 非常诧异,不仅诧异于他的小猫咪居然会说话,更诧异于这可爱的小不点为何有如此魔力?

魔法猫告诉 TT,它其实拥有一张游戏胜负表,上面有 N 个人以及 M 个胜负关系,每个胜负关系为 A B,表示 A 能胜过 B,且胜负关系具有传递性。即 A 胜过 B,B 胜过 C,则 A 也能胜过 C。

TT 不相信他的小猫咪什么比赛都能预测,因此他想知道有多少对选手的胜负无法预先得知,你能帮帮他吗?

思路:
  • 考虑弗洛伊德算法可以用来解决传递闭包的大致想法: 弗洛伊德算法最初用于计算图中所有点对之间的最短路,针对每个点,它会不断尝试经过更多的中间节点,判断其是否缩短了距离. 最终当所有点都尝试完毕, 结果即为最短路. 对于关系来说, 这种不断扩展经过中间节点的过程其实就是尝试建立关系的过程.
  • 上课时看到题目想到如果dia[a][b]==0那么a,b应该就不能判断胜负关系了. 显然这种想法是错误的: 每一个胜负关系是有序的, dis[a][b]==0只能说明a不能胜过b, 但是对于b能否胜过a是未知的.(后者是由b出发计算闭包得到的结果)
  • 剪枝: 如果dis[i][k]==0那么其实后续的运算是没有意义的.
实现:
#include<iostream>
using namespace std;

int p,n,m,a,b;
bool dis[505][505];

int main()
{
	std::ios::sync_with_stdio(false);
	cin>>p;
	for(int i=0;i<p;++i)
	{
		int ans=0;
		cin>>n>>m;
				
		for(int i=1;i<=n;++i)
			for(int j=1;j<=n;++j)
				dis[i][j]=0;
			
		for(int k=0;k<m;++k)
		{
			cin>>a>>b;
			dis[a][b]=1;
		}
		for(int k=1;k<=n;++k)
			for(int i=1;i<=n;++i)
			if(dis[i][k]!=0)
			{//注意剪枝
				for(int j=1;j<=n;++j)
					dis[i][j]=dis[i][j]|(dis[i][k]&dis[k][j]);
			}		
		for(int i=1;i<=n;++i)
			for(int j=i+1;j<=n;++j)
			if(dis[i][j]==0 && dis[j][i]==0)ans++;
		cout<<ans<<endl;	
	}
	
	return 0;
}

TT 的旅行日记

众所周知,TT 有一只魔法猫。

今天他在 B 站上开启了一次旅行直播,记录他与魔法猫在喵星旅游时的奇遇。 TT 从家里出发,准备乘坐猫猫快线前往喵星机场。猫猫快线分为经济线和商业线两种,它们的速度与价钱都不同。当然啦,商业线要比经济线贵,TT 平常只能坐经济线,但是今天 TT 的魔法猫变出了一张商业线车票,可以坐一站商业线。假设 TT 换乘的时间忽略不计,请你帮 TT 找到一条去喵星机场最快的线路,不然就要误机了!

思路:
  • 这道题应该是无向加权图, 属于最短路问题. 考虑到我们需要从起点到终点耗时最短, 因此使用迪杰斯特拉算法. 比较不同的地方在于, 这道题的边有经济线和商业线两种, 因此需要区分开来. 比较直接的思路是枚举商业线, 记为(u,v,w), 那么我们需要计算从起点到u+v到终点+w,以及起点到v+u到终点+w. 取两者中的较小者,那么我们便得到经过此条商业线的最短路.
  • 枚举所有商业线, 再与只乘坐经济线的最短路进行比较,便可得到最终的目标最短路.
  • 注意路径的输出. 考虑到在跑迪杰斯特拉的过程中记录每个点的前序节点. 之后对于商业线, 需要注意其前后的路径顺序是不一样的,区分输出即可.
实现:
#include<iostream>
#include<queue>
#include<utility>
#include<algorithm>
#include<string.h>
#include<list>
using namespace std;

const int M=1010;
const int N=1010;
const int K=1010;
const int inf=1e7;

int _N,_S,_E,X,Y,Z; 
int eco,bus;//经济线与商业线路段数 
int ans_eco,ans_bus;//两种答案,前者代表只有经济线
int targetx,targety;//表示商业线的两个端点


list<int> path;
struct Edge{
	int to,next,w;
}e[10100];

int head[N],tot,n,m,vis[N];
int disv[N],disu[N];
int pre_v[N],preu[N];
void add(int x,int y,int w)
{
	e[++tot].to=y,e[tot].next=head[x];
	e[tot].w=w,head[x]=tot;
}

priority_queue<pair<int,int> >q;

void dijkstra(int s,int *dis,int *pre)
{
	while(q.size())q.pop();
	memset(vis,0,sizeof vis);
	for(int i=1;i<=_N;++i)dis[i]=inf;
	dis[s]=0;
	pre[s]=-1;
	q.push(make_pair(0,s));
	while(q.size())
	{
		int x = q.top().second;
		q.pop();
		if(vis[x])continue;
		vis[x]=1;
		for(int i=head[x];i;i=e[i].next)
		{
			int y=e[i].to,w=e[i].w;
			if(dis[y] > dis[x] + w)
			{
				dis[y] = dis[x] +w;
				pre[y] = x;
				q.push(make_pair(-dis[y],y));
			}
		}
	}
}


int main()
{
	std::ios::sync_with_stdio(false);
	bool first=0;
	while(cin>>_N>>_S>>_E)
	{
		if(first==1)cout<<endl;
		path.clear();
		tot=0;
		for(int i=1;i<=_N;++i)
		{
			head[i]=0;
			pre_v[i]=-1;
			preu[i]=-1;
		}
		cin>>eco;
		for(int i=0;i<eco;++i)
		{
			cin>>X>>Y>>Z;
			add(X,Y,Z);
			add(Y,X,Z);
		}
		cin>>bus;
		dijkstra(_S,disv,pre_v);
		dijkstra(_E,disu,preu);
		ans_eco=disv[_E];
		targetx=-1,targety=-1,ans_bus=ans_eco;
		for(int i=1;i<=bus;++i)
		{
			cin>>X>>Y>>Z;
			int templength1 = disv[X]+disu[Y]+Z;
			int templength2 = disv[Y]+disu[X]+Z;
			if(ans_bus>min(templength1,templength2))
			{
				ans_bus=min(templength1,templength2);
				if(templength1 < templength2)
				{
					targetx=X;
					targety=Y;
				}
				else
				{
					targetx=Y;
					targety=X;
				}
			}
		}
		if(targetx==-1)	
		{
			path.push_front(_E);
			int par=pre_v[_E];
			while(par!=-1)
			{
				path.push_front(par);
				par=pre_v[par];
			}
			cout<<_S;
			path.pop_front(); 
			while(!path.empty())
			{
				int tmp=path.front();
				path.pop_front();
				cout<<" "<<tmp;
			}
			cout<<endl;
			cout<<"Ticket Not Used"<<endl;
			cout<<ans_eco<<endl; 
		}
		else
		{
			//先输出前半部分,后半部分可以直接根据pre进行输出
			path.push_front(targetx);
			int par = pre_v[targetx];
			while(par!=-1)
			{
				path.push_front(par);
				par = pre_v[par];
			} 
			while(!path.empty())
			{
				cout<<path.front()<<" ";
				path.pop_front();
			}
			cout<<targety;
			par = preu[targety];
			while(par!=-1)
			{
				cout<<" "<<par;
				par=preu[par];
			}
			cout<<endl;
			cout<<targetx<<endl;
			cout<<ans_bus<<endl;
		}
		first=1;//不是第一组
	 } 	
	return 0;
}
反思:
  • 注意输出的格式. 目标是最后一组之后不输出换行, 但是显然如果不输入_N,_S,_E没办法判断是否到达了最后一组数据. 因此我们需要在除第一组之外每组前面输出换行, 进行一个判断即可.
  • 刚开始考虑到经济线路段数不超过1000, 因此设置Edge数组大小为1010, 但是出现RE, 后面才意识到由于是无向图, 每条路段存了两次, 更改大小之后正确.
  • 需要注意对于商业线(u,v,w)既可能是从S到u也可能是S到v, 这两种情况都需要考虑. 但是一开始没有想到, 助教上课说了才意识到.

TT 的美梦

这一晚,TT 做了个美梦!

在梦中,TT 的愿望成真了,他成为了喵星的统领!喵星上有 N 个商业城市,编号 1 ~ N,其中 1 号城市是 TT 所在的城市,即首都。

喵星上共有 M 条有向道路供商业城市相互往来。但是随着喵星商业的日渐繁荣,有些道路变得非常拥挤。正在 TT 为之苦恼之时,他的魔法小猫咪提出了一个解决方案!TT 欣然接受并针对该方案颁布了一项新的政策。

具体政策如下:对每一个商业城市标记一个正整数,表示其繁荣程度,当每一只喵沿道路从一个商业城市走到另一个商业城市时,TT 都会收取它们(目的地繁荣程度 - 出发地繁荣程度)^ 3 的税。

TT 打算测试一下这项政策是否合理,因此他想知道从首都出发,走到其他城市至少要交多少的税,如果总金额小于 3 或者无法到达请悄咪咪地打出 ‘?’。

思路:
  • 分析题目可知可能存在负环, 因此使用SPFA. 对于负环的判断, 如果一个点入队次数达到了n, 说明对应边已经被松弛了n次, 那么必然存在负环. 对于处于负环中并且可以经过负环到达的点, 它们都不存在最短路, 可以通过DFS或者BFS进行搜索标记, 在最后查询时只需判断是否被标记过即可.
实现:
#include<iostream>
#include<queue>
#include<algorithm>
#include<cmath>
using namespace std;

int INF=1e9,N;
int a[210]; 


struct Edge{
	int to,next,w;
}e[100010];
int head[210],tot,m,vis[210];
void add(int x,int y,int w)
{
	e[++tot].to=y,e[tot].next=head[x];
	e[tot].w=w,head[x]=tot;
}


bool flag[210]={0};
void dfs(int v)
{
	flag[v]=1;
	for(int i=head[v];i!=-1;i=e[i].next)
	{
		if(!flag[e[i].to])
			dfs(e[i].to);
	}
}


int dis[210],cnt[210];
void SPFA(int s)
{
	queue<int> q;
	while(!q.empty()) q.pop();
    for(int i=1;i<=N;i++)
        dis[i]=INF,vis[i]=0,cnt[i]=0,flag[i]=0;        
    dis[s]=0;
    q.push(s);                            
    vis[s]=1;
    while(!q.empty())
    {
        int u=q.front();                   
        q.pop();
        vis[u]=0;
        for(int i=head[u];i!=-1;i=e[i].next)  
        {
            int v=e[i].to,w=e[i].w;
            if(dis[v]>dis[u]+w)
            {
            	cnt[v]=cnt[u]+1;
            	if(cnt[v]>=N)
            	{//dfs对于负环可到达的点进行标记
            		dfs(v);
				}
                dis[v]=dis[u]+w;            
                if(!vis[v] && !flag[v])                 
                {
                    q.push(v);              
                    vis[v]=1;
                }
            }
        }
    }
}

int main()
{
	ios::sync_with_stdio(false);
	int T,M,A,B,Q,P;
	cin>>T;
	for(int i=0;i<T;++i)
	{
		tot=0;	
		cin>>N;
		for(int j=1;j<=N;++j)
			head[j]=-1;
		for(int j=1;j<=N;++j)
			cin>>a[j];
		cin>>M;
		for(int j=0;j<M;++j)
		{
			cin>>A>>B;
			int tmp=pow(a[B]-a[A],3);
			add(A,B,tmp);
		}
		
		SPFA(1);
		cout<<"Case "<<i+1<<":"<<endl;
		cin>>Q;
		for(int j=0;j<Q;++j)
		{
			cin>>P;
			if(flag[P]==true || dis[P]<3 || dis[P]==INF)
			cout<<"?"<<endl;
			else cout<<dis[P]<<endl;
		}
	}
	
	return 0;
}

总结:
  • 这周主要学习了最短路的四个算法, 包括迪杰斯特拉算法/弗洛伊德算法/贝尔曼福德算法及其对应的队列优化SPFA. 需要注意其正确性的理解/适用范围/代码实现等.
  • 注意每次有多组数据时, 需要注意初始化的过程, 要考虑得比较全面, 这次在这里犯了错误.
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值