程序设计思维与实践 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) 等价,即每一个二元组只被计算一次。

example

输入

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

输出

0
0
4

思路

我们用一个二维数组dis[A][B] 来记录A和B之间的胜负关系,dis[A][B] =1 代表A,B之间的关系确定(包括A>B,B>A)。

考虑关系的传递性,即 A>B, B>C ==> A>C , 则对于A,B 只要存在一个i,A > i && i > B 那么A,B关系确定。

我们用类似floyd算法的思想,对每个i,j对遍历其可能的中间桥梁k:
dis[i][j]=max(dis[i][j], dis[i][k]&dis[k][j]); 只有当dis[i][k]和dis[k][j]都为1,dis[i][j]才为1;

这里注意剪枝优化,即如果对于第二层的dis[i][k]=0,那么第三层j的循环对于dis[i][j]的改变没有贡献。应该continue直接跳过。(另外也存在对floyd本身的优化,如利用矩阵对称性等)。

代码

#include<iostream>
#include<algorithm>
#include<stdio.h>
#include<string>
#include<string.h>
using namespace std;
/*
胜负关系表,输出无法预知胜负选手对;
传递闭包,Floyd;
剪枝优化 
*/ 
int n,m;
int dis[510][510];
 
void floyd_1(){
    for(int k=1;k<=n;k++)
	  for(int i=1;i<=n;i++){
	  	if(dis[i][k] !=0 )//dis[i][k]为0时dis[i][j]无更新 
	    for(int j=1;j<=n;j++){
		    if(dis[i][j]==1)continue;
		    dis[i][j]=max(dis[i][j], dis[i][k]&dis[k][j]);
		}
	}
} 

int main()
{
	int T,total,able=0;
	cin>>T;
	while(T--){
		able=0;
        scanf("%d%d",&n,&m);
        total=n*(n-1)/2;//总对数; 
        
	    for(int i=1;i<=n;i++)
	        for(int j=1;j<=n;j++)
	            dis[i][j]=0;//初始化; 
	    
		while(m--){
			int a,b;
			scanf("%d%d",&a,&b);
			dis[a][b]=1;//a>b; 
		}
		
		floyd_1();
		for(int i=1; i<=n; i++)
		    for(int j=i+1; j<=n; j++){    
			    //cout<<i<<' '<<j<<' '<<dis[i][j]<<endl;
				able += dis[i][j] || dis[j][i];//双向有一个确定--已知;
	        }
		cout<<total-able<<endl; 
    }
	return 0;
} 

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 前往喵星机场花费的总时间。

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

example

输入

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

思路

在这里插入图片描述
我们可以将整个图分开,其实是每一条商业线<x,y>可以将图分成两部分,左边包含起点S的部分和右边包含终点E的部分。整体的最短路可以从分层最短路思考。

先分别从S,E两点出发两次Dijkstra遍历整个图,path1[520] , dis1[520] 记录从S出发的路径前序点以及每个点距离S的最远距离。path2[520] , dis2[520] 类似,注意path2[520] 记录的是从终点E往前搜索的每个点的前驱点,在最后的输出路径中需要注意。

现在我们遍历每一条商业边。记录用使用商业边时的最短路length以及商业边的起点终点。

        int length=inf;
		for(int i=0; i<K; i++){//枚举每条商业线
			scanf("%d%d%d",&x,&y,&z);
            int p1 = dis1[x] + dis2[y] +z;//折返与不折返 
            int p2 = dis1[y] + dis2[x] +z;
			if( length > min(p1,p2) ){
			  if(p1 < p2){
				  length = p1, swith1=x, swith2=y;
			  }
			  else{
			  	  length = p2, swith1=y, swith2=x;
			  }
		   }
		}

最后与不采用商业边的最短路进行长度比较。分两种情况输出路径。从前往后我们用递归调用,从path2中输出直接while向前循环输出。注意输出格式。

代码

#include<iostream>
#include<algorithm>
#include<stdio.h>
#include<queue>
#include<string.h>
using namespace std;
/*
最优乘车线路,经济线,商业线; 
dijkstra

length没更新,add只加了一个方向,商业边没考虑折返;
换行控制 用putchar()???? 
*/
const int inf=1e8;//无穷大; 
int N,S,E,M,K;
struct Edge{//链式前向星; 
	int to,next,w;
    Edge(){};
}e[2020];

//最小堆
//priority_queue< int, vector<int>, greater<int> > q; 
priority_queue< pair<int ,int> > q;//后边插入dis的负值实现最小; 
int head[520],tot;
int vis[520];
int path1[520] , dis1[520];//从起点距离,路径; 
int path2[520] , dis2[520];//从终点距离,路径;
bool flag=false; 
 
void add(int x,int y, int z){
	tot++; e[tot].to=y; e[tot].w=z;
	e[tot].next=head[x];
	head[x]=tot;//现在点x出来的第一条边序号; 
}

void dijkstra(int s, int *dis, int *path){//s源点 
	while(q.size()) q.pop();	
	for(int i=1; i<=N; i++)
	    vis[i]=0,dis[i]=inf,path[i]=0;    
	    
	dis[s]=0;
	q.push(make_pair(0,s));
	//主要部分;
	while(q.size()){
		int x=q.top().second;//堆中dis最小值; 
		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){
				path[y]=x;//前序节点; 
				dis[y]=dis[x]+w;
				q.push(make_pair(-dis[y],y));
			}
		} 	
	}	 
} 

void output(int end,int *path){//递归从后输出路径 
	if(end == S){
	    cout<<S; return;
	}
	output(path[end],path);
	cout<<' '<<end;
}

int main(){
	int x,y,z;
	while(scanf("%d%d%d",&N,&S,&E) != EOF)
	{
		tot=0; 
	    memset(head,0,sizeof(head));

		scanf("%d",&M);//经济线 
		for(int i=0; i<M; i++){
			scanf("%d%d%d",&x,&y,&z);
			add(x,y,z); 
			add(y,x,z);
		}

		dijkstra(S, dis1, path1);//从起点 
		dijkstra(E, dis2, path2);//从终点

		int swith1=0,swith2=0; 
		scanf("%d",&K); 
		int length=inf;
		for(int i=0; i<K; i++){//枚举每条商业线
			scanf("%d%d%d",&x,&y,&z);
            int p1 = dis1[x] + dis2[y] +z;//折返与不折返 
            int p2 = dis1[y] + dis2[x] +z;
			if( length > min(p1,p2) ){
			  if(p1 < p2){
				  length = p1, swith1=x, swith2=y;
			  }
			  else{
			  	  length = p2, swith1=y, swith2=x;
			  }
		   }
		}

        if(!flag) flag=true;//格式控制
        else putchar('\n');
        
		if(length > dis1[E]){//不用ce最短 
			output(E,path1); 
            cout<<endl;
			cout<<"Ticket Not Used"<<endl; 
		    cout<<dis1[E]<<endl;
		}
		else{//用ce最短 
			output(swith1,path1);//前一段 
			int np=swith2;
			while(np != E){//后一段 
				cout<<' '<<np;
				np=path2[np];
			}
			cout<<' '<<np<<endl;
			cout<<swith1<<endl;
			cout<<length<<endl;
		}
	}
	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 则输出 ‘?’。

example

输入

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:
?
?

思路

思路和B题类似但是考虑到存在负权边再用Dijkstra可能出错,我们用SPFA,这里涉及负权环的判断。 vis[N],dis[N],cnt[N],pointW[N] 分别记录顶点是否在队列中,顶点距离源点最短距离,到达每个顶点的最短路中边的个数,顶点权值。

在寻找的过程中如果发现到达一个点的最短边大于等于顶点数,那么存在负权环,在该点所在的联通图中的所有顶点经过无限次都会变成负无穷,所以这些点都不能被搜索到了,用dfs寻找这些点,无法搜索的点还包括dis小于3的那些点。

if(cnt[y]>=n)
{//SPFA中路径数大于等于点数说明有负环 ,y所在的联通构件 点dis变为-inf
	dfs(y);			 
} 

代码

#include<iostream>
#include<stdio.h>
#include<algorithm>
#include<queue>
#include<cstring> 
#include<string.h>
#include<math.h>
using namespace std;
/*
C TT 美梦 
单源最短路bellman-ford 处理负权变并判断负环
SPFA优化
*/ 
const int N=210;
const int inf=1e8;
struct Edge{
	int to,next,w;
}edge[100010];

int head[N], tot, n, m, T; 
int vis[N],dis[N],cnt[N],pointW[N];//是否在队列,最短距离,最短路边数; 
queue<int> q;

void init(){//初始化 
	tot=0;
	memset(head,0,sizeof(head));
	memset(vis,0,sizeof(vis));
	memset(cnt,0,sizeof(cnt));
	for(int i=1;i<=n;i++) dis[i]=inf;
}

void add(int x,int y,int z){
	tot++; edge[tot].to=y; edge[tot].w=z;
	edge[tot].next=head[x];
	head[x]=tot;
} 
void dfs(int x){
	//所有能被x到达的点都标记成负环上的点
	//负环中的点不能入队 
	dis[x]=-inf;	
	for(int i=head[x];i;i=edge[i].next){
		if(dis[edge[i].to]!=-inf){
			dfs(edge[i].to);
		}		
	} 
}

void Spfa(int s){
	while(q.size()) q.pop();
	dis[s]=0;
	vis[s]=1;
	q.push(s) ;
	
	while(q.size()){
		int x=q.front();
		//x出列; 
		q.pop();	
		vis[x]=0;
		if(dis[x]==-inf) continue;		
		for(int i=head[x]; i; i=edge[i].next){//SPFA
			int y=edge[i].to;
			if(dis[y]>dis[x]+edge[i].w){
				dis[y]=dis[x]+edge[i].w;				
				cnt[y]=cnt[x]+1;//路径边数加1; 
				if(cnt[y]>=n){//SPFA中路径数大于等于点数说明有负环 ,y所在的联通构件 点dis变为-inf
					dfs(y);			 
				} 
				if(!vis[y]&&dis[y]!=-inf){// 加入新点; 
					vis[y]=1;
					q.push(y);
				}
			}
		} 		
	}
}
int main(){
	cin>>T;
	int index=0;
	while(T--)
	{
		index++;//组数加一; 
		
		scanf("%d",&n);
		init();
		for(int i=1;i<=n;i++){//点权; 
			cin>>pointW[i];
		}
		
		scanf("%d",&m);
		int x,y;
		while(m--){
			scanf("%d%d",&x,&y);
			add(x,y,pow ( pointW[y]-pointW[x] , 3) );
		}	
			
		Spfa(1);
		//查询; 
		int q=0,p=0;
		scanf("%d",&q);
		cout<<"Case "<<index<<":"<<endl;
		while(q--){//查询; 
			cin>>p;			
			if(dis[p]<3 || dis[p]==inf)cout<<"?"<<endl;
			else cout<<dis[p]<<endl;
		} 
	}
}

总结

A题中的剪枝优化思想自己还没有这种意识,在TL时可以往这方面想一想;
B题自己的主要问题是输出格式不对,没有很好的理解每组间输出换行。最后一组后边是不能输出换行的,应该单独开个标记符控制。 往最大堆中插入负数实现最小堆很巧妙。
C题dijkstra不能解决负权边问题,SPFA中有判断负权环。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值