WEEK 7作业 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 不相信他的小猫咪什么比赛都能预测,因此他想知道有多少对选手的胜负无法预先得知,你能帮帮他吗?

输入输出格式及样例

Input
第一行给出数据组数。
每组数据第一行给出 N 和 M(N , M <= 500)。
接下来 M 行,每行给出 A B,表示 A 可以胜过 B。
Output
对于每一组数据,判断有多少场比赛的胜负不能预先得知。注意 (a, b) 与 (b, a) 等价,即每一个二元组只被计算一次。
Input
3
3 3
1 2
1 3
2 3
3 2
1 2
2 3
4 2
1 2
3 4
Output
0
0
4

思路

  由于胜负具有传递性,a胜过b,b胜过c,那么a就能胜过c,所以本题就是一个求传递闭包的过程,使用floyd算法求多源最短路的思想,将最短路的修改改为dis[i][j] = (dis[i][j] | (dis[i][k] & dis[k][j])),就可以求出每个点对其他点的胜负情况,因为初始话dis都为false,如果中间点k输给了i却赢过了j,则i,j的胜负关系就很明朗了,使用与操作就是保证两者都为1结果才为1,所以最终二维数组的表现是,如果dis[i,j]=true,表示i能赢j,false表示不确定,如果dis[i,j],dis[j,i]都为false就表示两者比赛胜负不可预测。求出所有这样的值除以二即可。

实验代码

#include <iostream>
#include <cstring>
using namespace std;
int N, M;
bool dis[501][501];
void floyd() {
	for (int k = 1; k <= N; k++)
		for (int i = 1; i <= N; i++) {
			if (dis[i][k]) {
				for (int j = 1; j <= N; j++)
					dis[i][j] = (dis[i][j] | (dis[i][k] & dis[k][j]));
			}
		}
}
int main(){
	int n;
	cin >> n;
	while (n > 0) {
		cin >> N >> M;
		memset(dis, false, sizeof(dis));
		int a, b;
		for (int i = 0; i < M; i++) {
			cin >> a >> b;
			dis[a][b] = true;

		}
		floyd();
		int count = 0;
		for (int i = 1; i <= N; i++)
			for (int j = i + 1; j <= N; j++)
				if (dis[i][j] == false && dis[j][i] == false)count++;
		cout << count << endl;
		n--;
	}
	return 0;
}

B-TT的旅行日记

题目描述

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

输入输出格式及样例

Input
输入包含多组数据。每组数据第一行为 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 行是商业线路段的描述,格式同经济线。
所有路段都是双向的,但有可能必须使用商业车票才能到达机场。保证最优解唯一。
Output
对于每组数据,输出3行。第一行按访问顺序给出 TT 经过的各个车站(包括起点和终点),第二行是 TT 换乘商业线的车站编号(如果没有使用商业线车票,输出"Ticket Not Used",不含引号),第三行是 TT 前往喵星机场花费的总时间。
本题不忽略多余的空格和制表符,且每一组答案间要输出一个换行
Input
4 1 4
4
1 2 2
1 3 3
2 4 4
3 4 5
1
2 4 3
Output
1 2 4
2
5

思路

本题是基于图的问题,由于涉及到权值为题,我们建立链式前向星描述图,本题如果没有商业线,就是一个单纯的求最短路的问题,加了商业线就不同的,因为商业线的乘坐次数有限,本题只限一次,所以我们可以用两次dijkstra算法求出源点到各点,终点到各点的最短距离,然后遍历每一条商业线,将起点到商业线的其中一个端点的距离,加上终点到另一个端点的距离,加上商业线的权值,就可以得出使用商业线的距离,由于本题是无向图,所以要交换端点再求一次,这样求出使用该商业线的最短距离,再与不用商业线的最短距离进行比较,使用小的,将商业线遍历完就可以得到最短路。
但最后要注意输出路径的时候,前半段是源点到中间点,后半段是终点到中间点,如果使用的是pre数组要倒着输出。

实验代码

#include <iostream>
#include <stdio.h>
#include <cstring>
#include <queue>
#include <vector>
using namespace std;
const int inf = 101;
const int maxn = 500;
int n, N, S, E, M, K;
struct Edge {//边
	//int u;
	int v;
	int w;
	int nxt;
}edge[maxn * maxn];
int head[maxn], tot;//链式前向星
void init() {
	tot = 0;
	memset(head, -1, sizeof(head));
}
void add(int u, int v, int w) {
	edge[++tot].v = v;
	edge[tot].w = w;
	edge[tot].nxt = head[u];
	head[u] = tot;
}
int vis[maxn];
priority_queue<pair<int, int> > q;
void dijkstra(int s,int* dis,int *path) {
	while (q.size()) q.pop();
	memset(vis, 0, sizeof(vis));
	for (int i = 1; i <= N; i++) dis[i] = inf;
	dis[s] = 0;
	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 != -1; i = edge[i].nxt) {
			int y = edge[i].v, W = edge[i].w;
			if (dis[y] > dis[x] + W) {
				path[y] = x;
				dis[y] = dis[x] + W;
				q.push(make_pair(-dis[y], y));
			}
		}
	}
}
int dis1[maxn], dis2[maxn], path1[maxn], path2[maxn];
int main(){
	while (cin >> N >> S >> E) {
		cin >> M;
		init();
		int u, v, w;
		for (int i = 0; i < M; i++) {
			scanf("%d%d%d", &u, &v, &w);
			add(u, v, w);
			add(v, u, w);
		}
		for (int i = 0; i <= N; i++) {
			dis1[i] = 0; dis2[i] = 0; path1[i] = 0; path2[i] = 0;
		}
		dijkstra(S, dis1, path1); dijkstra(E, dis2, path2);
		int k; cin >> k;
		int min = dis1[E], temp = 1, temp1 = 0, temp2 = 0;
;		for (int i = 0; i < k; i++) {
			scanf("%d%d%d", &u, &v, &w);
			int sum = dis1[u] + dis2[v] + w;
			if (min > sum) {
				min = sum; temp1 = u; temp2 = v; temp = 0;
			}
			sum = dis1[v] + dis2[u] + w;
			if (min > sum) {
				min = sum; temp1 = v; temp2 = u; temp = 0;
			}
		}
		vector<int> path;
		int i = temp1;
		if (temp == 0) {
			path.push_back(temp1);
			if (temp1 != S) {
				while (path1[i] != S) {
					path.insert(path.begin(), path1[i]);
					i = path1[i];
				}
				path.insert(path.begin(), path1[i]);
			}
			path.push_back(temp2); i = temp2;
			if (temp2 != E) {
				while (path2[i] != E) {
					path.push_back(path2[i]);
					i = path2[i];
				}
				path.push_back(E);
			}
			int n = path.size();
			for (int i = 0; i < n; i++)cout << path[i] << " ";
			cout << endl;
			cout << temp1 << endl;
		}
		else {
			cout << S << " "; i = S;
			while (path2[i] != E) {
				cout << path2[i] << " ";
				i = path2[i];
			}
			cout << E << endl;
			cout << "Ticket Not Used" << endl;
		}
		cout << min << endl << endl;
	}
	return 0;
}

C-TT的美梦

题目描述

这一晚,TT 做了个美梦!
在梦中,TT 的愿望成真了,他成为了喵星的统领!喵星上有 N 个商业城市,编号 1 ~ N,其中 1 号城市是 TT 所在的城市,即首都。
喵星上共有 M 条有向道路供商业城市相互往来。但是随着喵星商业的日渐繁荣,有些道路变得非常拥挤。正在 TT 为之苦恼之时,他的魔法小猫咪提出了一个解决方案!TT 欣然接受并针对该方案颁布了一项新的政策。
具体政策如下:对每一个商业城市标记一个正整数,表示其繁荣程度,当每一只喵沿道路从一个商业城市走到另一个商业城市时,TT 都会收取它们(目的地繁荣程度 - 出发地繁荣程度)^ 3 的税。
TT 打算测试一下这项政策是否合理,因此他想知道从首都出发,走到其他城市至少要交多少的税,如果总金额小于 3 或者无法到达请悄咪咪地打出 ‘?’。

输入输出格式及样例

Input
第一行输入 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 号点的最少税费。
Output
每个询问输出一行,如果不可达或税费小于 3 则输出 ‘?’。
Input
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
Output
Case 1:
3
4
Case 2:

思路

先引入spfa算法,spfa算法是可以解决边权值为负值单源最短路,思想我觉得和前序遍历有点相似,从起点开始入队列,然后将他的邻接点入队列,保证每个点只入队列一次,每次出队列,期间不断更新起点到队首元素邻接点的距离为最短,方法是起点到队首元素的距离加上该边权值就是起点到队首元素邻接点的距离,为了保证最短,每次加上判断即可,同时还有一个数组记录,起点到每个点最短距离所经过的点数,如果点数大于了总点数,说明这里出现了负环,也就是权值和为负数的环导致求最短距离的时候在环里面绕,对于这种情况说明求不出到该点的最短距离,执行相应的处理工作即可。
本题就是一个spfa算法的例子,本题的边权是两者的差,没有给出两者的大小,所以可能是负数,所以本题采用spfa算法,,由于本题要求不能到达的点,也就是求负环里面的点,我们求出一个负环里面的点,然后对他进行dfs即可。

实验代码

#include <iostream>
#include <algorithm>
#include <queue>
using namespace std;
const int inf = 100000;
const int maxn = 200;
int inq[maxn], cnt[maxn], dis[maxn];
struct Edge {//边
	int v;
	int w;
	int nxt;
}edge[1000000];
int head[maxn], tot;
void add(int u, int v, int w){
	edge[++tot].v = v;
	edge[tot].w = w;
	edge[tot].nxt = head[u];
	head[u] = tot;
}
int vis[maxn];
void dfs(int s){
	for (int i = head[s]; i != -1; i = edge[i].nxt){
		int y = edge[i].v;
		if (!vis[y]){
			vis[y] = 1;
			dfs(y);
		}
	}
}
void spfa(int s, int n){
	queue<int> q;
	for (int i = 1; i <= n; i++){
		inq[i] = 0;
		cnt[i] = 0;
		dis[i] = inf;
	}
	dis[s] = 0;
	inq[s] = 1;
	q.push(s);
	while (!q.empty()){
		int u = q.front();q.pop();
		inq[u] = 0;
		for (int i = head[u]; i!=-1; i = edge[i].nxt){
			int v = edge[i].v;
			int w = edge[i].w;
			if (dis[v] > dis[u] + w){
				cnt[v] = cnt[u] + 1;
				if (cnt[v] >= n){
					dfs(v); continue;
				}
				dis[v] = dis[u] + w;
				if (!inq[v]){
					q.push(v);
					inq[v] = 1;
				}
			}
		}
	}
}

int main(){
	//cin.sync_with_stdio(false);
	int T;cin >> T;
	int count = 1;
	while (T--){
		int n; cin >> n;
		tot = 1;
		for (int i = 1; i <= n; i++) {
			vis[i] = false;
			head[i] = -1;
		}
		int* a = new int[n + 1];
		for (int i = 1; i <= n; i++)
			cin >> a[i];
		int m; cin >> m;
		for (int i = 0; i < m; i++){
			int b, c;
			cin >> b >> c;
			int w = (a[c] - a[b]) * (a[c] - a[b]) * (a[c] - a[b]);
			add(b, c, w);
		}
		spfa(1, n);
		int Q; cin >> Q;
		cout << "Case " << count <<":"<< endl; count++;
		for (int i = 0; i < Q; i++){
			int p; cin >> p;
			if (dis[p] == inf || vis[p] || dis[p] < 3)
				cout << "?" << endl;
			else
				cout << dis[p] << endl;
		}
	}
	return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值