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) 等价,即每一个二元组只被计算一次。
Sample Input
3
3 3
1 2
1 3
2 3
3 2
1 2
2 3
4 2
1 2
3 4
Sample Output
0
0
4
分析
题目中有N , M <= 500,所以O(n3)的复杂度是可行的,同时,因为胜负关系具有传递性,可以用Floyd算法求出任意两点的胜负关系(传递闭包),但是仍旧要进行剪枝操作
if (dis[i][k]==1)for (int j = 1; j <= n; j++),否则会超时。这里用的dis[a][b]表示,意思分别如下:
- dis[a][b] = 1表示a比b强
- dis[a][b] = 0表示a与b的胜负关系不明
- dis[a][b] = 0&&dis[b][a] = 0表示a与b的胜负关系无法预先判断
所以在最后判断有多少场比赛的胜负不能预先得知时,用的是if (dis[k][k1] == 0 && dis[k1][k] == 0)result++;
C++
#include<stdio.h>
#include<string.h>
using namespace std;
int dis[510][510];
void Floyd(int n) {
for (int k = 1; k <= n; k++)
for (int i = 1; i <= n; i++)
if (dis[i][k]==1)//剪枝,否则超时
for (int j = 1; j <= n; j++)
if(i!=j&&k!=j&& dis[i][j] == 0)//可以适当剪枝,dis[i][j] == 1时不需要处理
dis[k][j] == 1 ? dis[i][j] = 1 : dis[i][j] = 0;
}
int main() {
int groups;
scanf("%d",&groups);
for (int i = 0; i < groups; i++) {
int m, n, result = 0;
memset(dis,0, sizeof(dis));
scanf("%d%d", &n ,&m);
for (int j = 0; j < m; j++) {
int a, b;
scanf("%d%d", &a, &b);
dis[a][b] = 1;//a可以胜过b
}
Floyd(n);
for (int k = 1; k <= n; k++)
for (int k1 = k + 1; k1 <= n; k1++)//只进行对角线右上方部分的处理(对称,可少处理部分)
if (dis[k][k1] == 0 && dis[k1][k] == 0)result++;//但是也必须要当对角线上下都不能确定胜负关系时才算一种不确定情况,因为可能会有对角线下方的胜负关系是已知的
printf("%d\n",result);
}
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 前往喵星机场花费的总时间。
本题不忽略多余的空格和制表符,且每一组答案间要输出一个换行
Sample Input
4 1 4
4
1 2 2
1 3 3
2 4 4
3 4 5
1
2 4 3
Sample Output
1 2 4
2
5
分析
该题有两种思路:
-
这里用的是思路2,对单源最短路进行变形,基于Dijkstra算法,还是用到了链式前向星。
-
变形后用到了pre[y][ff] = { x,f };二维pair类型数组,用来存y点前一个点的id和fl属性值,ff为1表示该点是商业线所到的点及其之后的点,ff为0则为之前的经济线中的点,存起来是为了最后能够方便地找到换乘商业线的车站编号。其中要注意ff的赋值:int ff = flag | f;//来源点的f和现在点的flag其中一个为1后,后续的一系列dis,pre二维都为1了(代表商业线)。
-
还有要注意优先队列默认是一个最大堆,每次输出的堆顶元素是此时堆中的最大元素,因为要得到每次队列中distance的最小值,否则可能会先取出队列中最后面的几个点,而此时中间路段的dis并未更新,仍为inf导致错误结果,所以要对优先队列进行自定义排序,注意自定义排序时的表示方法,要用**>**,即bool operator < (const Node &b) const {return distance > b.distance;}表示distance值较大的Node优先级低(distance小的Node排在队前)。
-
最后注意换乘商业线的车站编号的查找方法。从后往前找,z初始化为1,pre的second不断更新z值(换乘到的点的后一个点及之后的点pre的second均为1),如果发现z变为0,而changeSite此时仍为0(初始化为0,此时表示第一次变化),表示z第一次变为0,此时pre的first正是换乘商业线的车站编号。
C++
#include<iostream>
#include<stdio.h>
#include <queue>
#include<cstring>
using namespace std;
const int N = 1100;
const int inf = 2020000;
struct Node {
int id, distance, fl;
Node(int a, int b, int c) :id(a), distance(b), fl(c) {};
bool operator < (const Node &b) const {
return distance > b.distance;//注意是用>,是根据dis从小到大进行自定义排序
}
};
struct Edge
{
int to, next, w, flag;
}edge[5000];
int head[N], tot, n, vis[N][2], dis[N][2], changeSite, s, e;
pair<int, int> route[N], pre[N][2],temp;
void init() {
tot = changeSite = 0;
memset(head, -1, sizeof(head));
memset(vis, 0, sizeof(vis));
memset(pre, 0, sizeof(pre));
memset(route, 0, sizeof(route));
}
void add(int x, int y, int w,int flag) {
edge[++tot].to = y, edge[tot].next = head[x],edge[tot].flag = flag;
edge[tot].w = w, head[x] = tot;
}
priority_queue<Node> q;
void dijkstra(int s) {
while (q.size()) q.pop();
for (int i = 1; i <= n; i++) dis[i][0] = dis[i][1] = inf;
dis[s][0] = dis[s][1] = 0;
q.push(Node(s,0,0));
while (q.size()) {
int x = q.top().id; int f = q.top().fl;
q.pop();
if (vis[x][f]) continue;
vis[x][f] = 1;
for (int i = head[x]; i!=-1; i = edge[i].next) {
int y = edge[i].to, w = edge[i].w, flag = edge[i].flag;
if (flag & f) continue;//flag和f都为1时,舍去
int d = dis[x][f] + w;
int ff = flag | f;//flag和f其中一个为1后,后续的一系列dis,pre二维都为1了(商业线)
if (dis[y][ff] > d) {
pre[y][ff] = { x,f };//存前一个点及其fl值
dis[y][ff] = d;
q.push(Node(y,d,ff));
}
}
}
}
int main() {
int count0 = 0;
while (cin >> n >> s >> e) {
if (count0++)printf("\n");//注意输出的格式!
init();
int m; cin >> m;
for (int i = 0; i < m; i++) {
int x, y, z;
cin >> x >> y >> z;
add(x, y, z, 0);add(y, x, z, 0);
}
int k; cin >> k;
for (int i = 0; i < k; i++) {
int x, y, z;
cin >> x >> y >> z;
add(x, y, z, 1); add(y, x, z, 1);
}
dijkstra(s); int index = 0; int x = e;
if (dis[e][0] < dis[e][1]) {
route[0] = { e,0 };
for (int i = e; pre[i][0].first!=0; i = pre[i][0].first) {
route[++index]= {pre[i][0].first,0 };
}
printf("%d", route[index].first);
while (index)printf(" %d", route[--index].first);
printf("\nTicket Not Used\n");
printf("%d\n", dis[e][0]);
}
else {
int i = e,z = 1;
while(i) {
route[++index] = { i,1 };
temp = pre[i][z];//必须定义一个temp,因为下一行i或者z都会进行更改
i = temp.first; z = temp.second;
if (!(z | changeSite)) { changeSite = i; }//即当z和changeSite都为0时,将此时的位置赋值给changeSite
}
for (int i = index; i >= 2; i--)
printf("%d ", route[i]);
printf("%d\n", route[1]);
printf("%d\n", changeSite);
printf("%d\n", dis[e][1]);
}
}
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 则输出 ‘?’。
本题不忽略多余的空格和制表符,且每一组答案间要输出一个换行
Sample 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
Sample Output
Case 1:
3
4
Case 2:
?
?
分析
该题我用的是邻接表(也可以用链式前向星)。
- 定义了vector< Edge > G[210];其中结构体Edge中含有int to, weight;表示到达的点及其边长。
- 这里用的是修改后的SPFA算法。SPFA 算法是 Bellman-ford 算法的队列优化,负环存在的判断条件与Bellman-ford 算法一致。而SPFA 中将点加入队列,不容易判断边的松弛次数,可以判断最短路的边数,如果到某一点的最短路的边数超过了n-1则说明有负环,这里v的最短路长度用cnt[v]数组记录,然后判断cnt[v]大于等于n的话就说明找到了负环。
SPFA算法相关内容大致是:
- 一定要注意,因为题目中是多组数组,每次输出新的数据后,init()中要进行G[n]数组的清空,而且必须是在for循环中一个个清空G[i]。
C++
#include<iostream>
#include<string.h>
#include<queue>
#include<vector>
using namespace std;
const int inf = 0x3f3f3f3f;
int t, n, m, u, v, qu, p, a[210];
queue<int> q;
int dis[210], inq[210], cnt[210], vis[210];
struct Edge {
int to, weight;
Edge(int x, int y) :to(x), weight(y) {};
};
vector<Edge> G[210];
void init() {
while (!q.empty()) q.pop();
for (int i = 1; i <= n; i++) {
dis[i] = inf;
inq[i] = 0;
cnt[i] = 0;
vis[i] = 0;
G[i].clear();//一定要记住!
}
}
void dfs(int u) {
vis[u] = 1;//标记
for (Edge e : G[u]) {
int v = e.to;
if (vis[v] == 0) dfs(v);//包含该点的路线全部标记上
}
}
void spfa(int s) {
dis[s] = 0;
inq[s] = 1;//入队标记
q.push(s);
while (!q.empty()) {
int u = q.front(); q.pop();
inq[u] = 0;
if (vis[u]) continue;
for (Edge e : G[u]) {
int v = e.to;
if (dis[v] > dis[u] + e.weight) {
cnt[v] = cnt[u] + 1;
if (cnt[v] >= n) dfs(v);//找到负环开始标记
dis[v] = dis[u] + e.weight;
if (!inq[v]) {
q.push(v);
inq[v] = 1;
}
}
}
}
}
int main() {
cin >> t;
int case_ = 1;
while (t--) {
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i];
init();
cin >> m;
for (int i = 0; i < m; i++) {
cin >> u >> v;
G[u].push_back(Edge(v, (a[v] - a[u])*(a[v] - a[u])*(a[v] - a[u])));
}
spfa(1);
cin >> qu;
cout << "Case" << " " << case_ << ":" << endl;
while (qu--) {
cin >> p;
if (vis[p] == 0) {
if (dis[p] < 3 || dis[p] == inf) cout << "?" << endl;//总金额小于 3 或者无法到达
else cout << dis[p] << endl;
}
else//标记上了(即存在负环)
cout << "?" << endl;
}
case_++;
}
return 0;
}