SDU week7作业(A TT的魔法猫 B TT的旅行日记 C TT的美梦 )

A :TT的魔法猫

题目

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

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

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

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

输入

第一行给出数据组数。

每组数据第一行给出 N 和 M(N , M <= 500)。

接下来 M 行,每行给出 A B,表示 A 可以胜过 B

输出

对于每一组数据,判断有多少场比赛的胜负不能预先得知。注意 (a, b) 与 (b, a) 等价,即每一个二元组只被计算一次。

样例

样例输入:

3
3 3
1 2
1 3
2 3
3 2
1 2
2 3
4 2
1 2
3 4

样例输出:

0
0
4

思路

这个就是利用了关系的传递性,如果i->k;k->j则i->j;
若AB 的关系已知,就是dis[A][B]=1或者是dis[B][A]=1;
使用floyd 算法,将原算法中的dis[i][j]=dis[i][k]+dis[k][j]改为dis[i][j]=dis[i][k]&dis[k][j]
注意的是:0(n3)的时间复杂度过高,因此需要剪枝,因为只有dis[i][k]和dis[k][j]都等于1 的时候结果才为1 ,因此当dis[i][k]的结果是0时,就可以break 。
存储方式是二维矩阵存储。

总结

floyd 算法的基本模板是:

void floyd()
{
for(int k=1;k<n;k++)
	for(int i =1;i<=n;i++)
		for(int j=1;i<=n;j++)
			dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);  //可以更改
}

复杂度是0(n3),
floyd 算法的应用

  • 用于求取图中任意两点之间的关系
  • 多源最短路问题,任意两点的距离关系
  • 图上的传递闭包,任意两点间的连通关系

代码

#include<stdio.h>
#include<string.h>
using namespace std;
#define maxn 510
int dis[maxn][maxn];
void floyd(int n)
{
	for(int k=1;k<=n;k++)
	{
		for(int i=1;i<=n;i++)
		{
			if(!dis[i][k]) continue;
			for(int j=1;j<=n;j++)
			{
				if(dis[i][j]<dis[i][k]&dis[k][j])
				{
					dis[i][j]=dis[i][k]&dis[k][j];    
				} 
			}
		}
	}
 } 
int main()
{
	int N;
	scanf("%d",&N); 
	while(N--)
	{
		memset(dis,0,sizeof(dis));
		int n,m;
		int a,b;
		scanf("%d %d",&n,&m);
		for(int i=1;i<=m;i++)
		{
			scanf("%d%d",&a,&b);
			dis[a][b]=1; 
		}
		floyd(n); 
		int cnt=0;
		for(int i=1;i<=n;i++)
		{
			for(int j=1;j<i;j++)
			{
				if(dis[i][j]!=1&&dis[j][i]!=1)
				{
					cnt++;
				}
			}
		}
		printf("%d\n",cnt);
	}
 } 

B :TT的旅行日记

题目

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

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

输入

输入包含多组数据。每组数据第一行为 3 个整数 N, S 和 E (2 ≤ N ≤ 500, 1 ≤ S, E ≤ 100),即猫猫快线中的车站总数,起点和终点(即喵星机场所在站)编号。

下一行包含一个整数 M (1 ≤ M ≤ 1000),即经济线的路段条数。

接下来有 M 行,每行 3 个整数 X, Y, Z (1 ≤ X, Y ≤ N, 1 ≤ Z ≤ 100),表示 TT 可以乘坐经济线在车站 X 和车站 Y 之间往返,其中单程需要 Z 分钟。

下一行为商业线的路段条数 K (1 ≤ K ≤ 1000)。

接下来 K 行是商业线路段的描述,格式同经济线。

所有路段都是双向的,但有可能必须使用商业车票才能到达机场。保证最优解唯一。

输出

对于每组数据,输出3行。第一行按访问顺序给出 TT 经过的各个车站(包括起点和终点),第二行是 TT 换乘商业线的车站编号(如果没有使用商业线车票,输出"Ticket Not Used",不含引号),第三行是 TT 前往喵星机场花费的总时间。

本题不忽略多余的空格和制表符,且每一组答案间要输出一个换行

样例

样例输入:

4 1 4
4
1 2 2
1 3 3
2 4 4
3 4 5
1
2 4 3

样例输出:

1 2 4
2
5

思路

求取两点间没有负权边的单源最短路问题,用dijkstra算法。比较大的一个问题是,需要有经济线和商业线的组合,但是由于最多只允许出现一条商业线,因此,可以从起点和终点分别使用dijkstra算法,求出到各点的距离,然后枚举商业线,看能否更新。
同时需要注意的是输出问题,因为需要正向输出记录的是前向数组,因此在从起点到终点的递归输出路径中要注意输出的位置,同时需不需要用商业线还需要分开处理。
存储方式:链式前向星
时间复杂度 : 0(n+m)log(n);

总结

dijkstra算法实现大体流程:

  • 设置s 为源点,dis[a]表示源点s 到a 的最短距离,初始化dis[s]=0,dis[i]=inf,将s 加入最小堆;

  • 每次从堆中取出一个点x,遍历x 的所有邻接边(x,y,w)比较dis[y]与dis[x]+w 的大小。(松弛操作)

  • 若松弛成功,更新dis[y] 的大小,将y 加入最小堆中

  • 不断执行上述操作,知道最小堆为空,最后得到的dis数组即为所求单源最短路径值。

需要注意的是:

  • 由于图中只有正边,因此每个点只会被最小堆弹出一次
  • 即一旦某个点被最小堆弹出,则不会被松弛,dis 的值为最短路

(粗浅的理解,由于每个值进去最小堆之前都是加入了一个正数,假如说再被弹出,一定是再被加入,这时候加的正数一定比原来的大,矛盾。因此只会被弹出一次,并且更新后的就是最短值)
模板中可以根据不同的条件改变松弛操作。

代码

#include<stdio.h>
#include<queue>
#include<string.h>
#include<stack> 
using namespace std;
#define maxn 1010
#define inf 200000
struct Edge
{
	int u,v,w,next;  
}edge[2*maxn];
Edge edge1[2*maxn]; 
int head[maxn];
int head1[maxn];  
int tot,tot1;
int dis[maxn];
int dis1[maxn];
int vis[maxn];
int path[maxn];
int path1[maxn]; 
void init(int n,int *head)
{
	tot=1;
	for(int i=1;i<=n;i++){
		head[i]=-1;
	}
}
void addEdge(int u,int v,int w)
{
	edge[tot].u=u;
	edge[tot].v=v;
	edge[tot].w=w;
	edge[tot].next=head[u];	
	head[u]=tot;
	tot++;
}
priority_queue<pair<int,int>> q;   //大根堆 
void dijkstra(int s,int *dis,int n,int *path)   //pair 从小到大 
{
	while(q.size())	q.pop();
	memset(vis,0,sizeof(vis));
	for(int i=1;i<=n;i++)	{
		dis[i]=-inf;
		path[i]=-1;
	}
	dis[s]=0;
	path[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=edge[i].next)
		{
			int y=edge[i].v,w=edge[i].w;
			if(dis[y]<dis[x]+w){
				dis[y]=dis[x]+w;
				path[y]=x;
				q.push(make_pair(dis[y],y));
			}
		}	
	} 
	 
} 
void outputS(int x,int S)
{
	if (x == S)
	{
		printf("%d", x);
		return;
	}
	outputS(path[x],S);
	printf(" %d", x);
}
void outputE(int x,int E)
{
	while (x != E)
	{
		printf(" %d", x);
		x = path1[x];
	}
	printf(" %d", x);
}
void Print(int S,int E)   //S-E
{
	
	stack<int> st;
	st.push(E);
	while(path[E]!=-1){
		st.push(path[E]);
		E=path[E];  //更新 
	}
	while(!st.empty()) {
		if(st.top()!=E)
			printf("%d ",st.top());  //多一个空格 
		else if(st.top()==E)
			printf("%d",st.top());
		st.pop();
	}
} 
int main()
{
	int N,S,E,M,K,u,v,w,uu=-1,vv=-1;
	int flag=0;
	while(scanf("%d%d%d",&N,&S,&E)!=EOF)
	{
		scanf("%d",&M);
		init(2*M+1,head);
		while(M--)
		{
			scanf("%d%d%d",&u,&v,&w);
			addEdge(u,v,-w);  //经济线 
			addEdge(v,u,-w);
		}
		 dijkstra(S,dis,N,path);       //
		 memset(vis,0,sizeof(vis));
		 dijkstra(E,dis1,N,path1);
		 bool bbb=false; 
		 int ans=dis[E];
		 scanf("%d",&K);
		 while(K--){
		 	scanf("%d%d%d",&u,&v,&w);
		  	if(ans<dis[u]-w+dis1[v]) {
		 		ans=dis[u]-w+dis1[v];
		 		uu=u;vv=v;
		 		bbb=true;
			}
			else if(ans<dis[v]-w+dis1[u]){
				ans=dis[v]-w+dis1[u];
	 			uu=v;vv=u;
	 			bbb=true;
			} 
		} 
		//输出路径
		if(flag>0)	printf("\n");
		if (!bbb)
		{
			outputS(E,S);
			printf("\nTicket Not Used\n");
			printf("%d\n", -dis[E]);
		}
		else
		{
			outputS(uu,S);//S-busa
			outputE(vv,E);//busb-E
			printf("\n%d\n%d\n", uu, -ans);
		}
		flag++;	 
	}
	return 0;
} 

C :TT的美梦

题目

这一晚,TT 做了个美梦!

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

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

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

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

输入

第一行输入 T,表明共有 T 组数据。(1 <= T <= 50)

对于每一组数据,第一行输入 N,表示点的个数。(1 <= N <= 200)

第二行输入 N 个整数,表示 1 ~ N 点的权值 a[i]。(0 <= a[i] <= 20)

第三行输入 M,表示有向道路的条数。(0 <= M <= 100000)

接下来 M 行,每行有两个整数 A B,表示存在一条 A 到 B 的有向道路。

接下来给出一个整数 Q,表示询问个数。(0 <= Q <= 100000)

每一次询问给出一个 P,表示求 1 号点到 P 号点的最少税费。

输出

每个询问输出一行,如果不可达或税费小于 3 则输出 ‘?’。

样例

样例输入:

2
5
6 7 8 9 10
6
1 2
2 3
3 4
1 5
5 4
4 5
2
4
5
10
1 2 4 4 5 6 7 8 9 10
10
1 2
2 3
3 1
1 4
4 5
5 6
6 7
7 8
8 9
9 10
2
3 10

样例输出:

Case 1:
3
4
Case 2:
?
?

思路

这个题中的边权相当于两个点差的3次方,因此极有可能出现负环,采用的使用队列的SPFA 算法。SPFA 算法通常用于求解含有负权边的单源最短路径同时可用于判断负环。

经过题意分析就是负环上的边和经过负环的边,总距离小于3 的,和不可达的三种情况输出“?"

SPFA 是选取松弛成功的边加入队列,由于有负权边的出现,一条边可能被弹出多次,但是由于只有n 个点,当弹出次数大于n 次的时候就出现了负环。据此来判断负环。当出现一个负环点的时候将这点做一次dfs ,标记,同时 加入队列的条件改为队列中没有且没有在负环上。

代码

#include<stdio.h>
#include<queue>
#include<string.h>
using namespace std;
# define maxn 100010 
# define inf  100000000
# define N 210
struct Edge{
	int u,v,w,next;
}E[maxn];
int a[N];
int head[N];
int tot;
int vis[N];
int vis1[N];
int cnt[N];
int dis[N]; 
void addEdge(int u,int v,int w)
{
	E[tot].u=u;
	E[tot].v=v;
	E[tot].w=w;
	E[tot].next=head[u];
	head[u]=tot;
	tot++;
} 
void dfs(int u)
{
	vis1[u]=1;  //错误点 RE 
	for(int i=head[u];i;i=E[i].next){
		if(!vis1[E[i].v]){
			dfs(E[i].v);
		}
	}	
} 
void SPFA(int n,int s)
{
	queue<int> q;
	for(int i=1;i<=n;i++)
	{
		vis[i]=0;
		cnt[i]=0;
		vis1[i]=0;
		dis[i]=100000000;   //数据范围不能超过int WA 
	} 
	dis[s] = 0;
	vis[s]=1;
	q.push(s);
	while(q.size()){
	 	int u =q.front(); q.pop();
	 	//每一个点可以被队列弹出很多次 
	 	vis[u] = 0;
	 	//松弛操作 
	 	for(int i=head[u];i;i=E[i].next){
	 		int v = E[i].v;
	 		if(dis[v] >dis[u]+E[i].w)
			 {
	 			dis[v] = dis[u] + E[i].w;
	 			cnt[v]++;    
	 			if(cnt[v]>n){  
	 				//出现了负环	 
	 				dfs(v);
	 				continue ;
				}    
				if(!vis[v]&&vis1[v]==0) {
				 	q.push(v);
				 	vis[v]=1;
				 }
			 }
		}
	 }
 } 
int main()
{
	int T,n,M,A,B,Q,P;
	scanf("%d",&T);
	for(int t=1;t<=T;t++)
	{
		scanf("%d",&n);
		for(int i=1;i<=n;i++)
		{
			scanf("%d",&a[i]);
		 } 
		scanf("%d",&M);
		tot=1;
		memset(head,-1,sizeof(head));
		while(M--)
		{
		 	scanf("%d%d",&A,&B);
		 	addEdge(A,B,(a[B]-a[A])*(a[B]-a[A])*(a[B]-a[A]));  //构造边 
		}
		// 算出来数组 res   从1号点到其他点的最小税费 
		SPFA(n,1);  
		scanf("%d",&Q);
		printf("Case %d:\n",t);
		for(int k=1;k<=Q;k++)
		{
			scanf("%d",&P);
			if(dis[P]<3||vis1[P]||dis[P]==inf) {
				printf("?\n");
			}	
			else 	
				printf("%d\n",dis[P]);
		} 
	}
	return 0;
}
发布了10 篇原创文章 · 获赞 0 · 访问量 164

猜你喜欢

转载自blog.csdn.net/weixin_45736432/article/details/105332843
今日推荐