程序设计思维与实践 Week7 作业

A TT的魔法猫

1. 题目大意

众所周知,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

2. 思路历程

  • 引入Floyd算法
  1. f[k][x][y]表示从节点x到节点y,中途只允许经过节点1到k,的最短路径长度
  2. f[n][x][y]则为节点x到节点y的最短路径长度(中途可经过1到n所有节点)
  3. 存在递推公式:f[k][x][y] = min{ f[k - 1][x][y], f[k - 1][x][k] + f[k - 1][k][y] }
    优化第一维后可得:f[x][y] = min{ f[x][y], f[x][k] + f[k][y] }
  4. 该算法的应用:
    1)求取图中任意两点之间的距离/关系
    2)对于图上的传递闭包,求取任意两点的连通关系
  • 这道题需要求解二者的胜负关系,而胜负关系具有传递性,因此可以用Floyd算法求出任意两点的胜负关系(传递闭包)
    dis[a][b] = 1 表示 a 胜 b,dis[a][b] = 0 表示 a 和 b 胜负关系不明

3. 具体实现

  • 函数void Floyd()实现Floyd算法,对于满足传递性的边计算闭包。
  • 注意dis[i][j] 和 dis[j][i] 要同时判断,因为二者中有任意一个已知即代表 i 和 j 的胜负关系已知。

4. 代码

#include <iostream>
#include <algorithm>
#include <cmath>
using namespace std;

int n, N, M;
int dis[555][555];

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++)
                if (dis[k][j])
                    dis[i][j] = 1;
            }
        }
    }
}

int main()
{
    cin >> n;
    for (int i = 0; i < n; i++)
    {
        memset(dis, 0, sizeof(dis));
        cin >> N >> M;
        int a, b;
        for (int j = 0; j < M; j++)
        {
            cin >> a >> b;
            dis[a][b] = 1;
        }
        Floyd();
        
        int cnt = 0;
        for (int i = 1; i <= N; i++)
        {
            for (int j = i + 1; j <= N; j++)
            {
                if (!dis[i][j] && !dis[j][i])
                    cnt++;
            }
        }
        
        cout << cnt << endl;
    }
    
    return 0;
}

B TT的旅行日记

1. 题目大意

众所周知,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

2. 思路历程

  • Dijsktra算法
  1. 设置 s 为源点,dis[a] 表示源点 s 到点 a 的最短路径,初始 dis[s] = 0, dis[i] = inf,将 s 加入小根堆
  2. 每次从堆中取出一个点 x,遍历 x 的所有邻接边,比较 dis[y] 和 dis[x] + w 的大小(松弛操作)
    1)若dis[y] > dis[x] + w 则松弛成功,需更新 dis[y] 的大小,再将 y 加入小根堆
    2)不断重复上述操作,直到小根堆为空,最后得到dis数组为单元最短路径
  3. 用于解决图中没有负边的单元最短路径
  • 这道题若除去商业线就是Dijkstra模版题,因此可以把商业线(u, v)和其他分开考虑
  1. 不使用商业线:起点到终点的最短路径
  2. 使用商业线:起点到 u 的最小距离 + 终点到商业线 v 的最小距离 + (u, v)
    或起点到 v 的最小距离 + 终点到商业线 u 的最小距离 + (u, v)
  • 使用两次Dijkstra,找到三种情况里距离最小的情况
  • 数组dis1 和 dis2 分别存储以起点和终点为源点得到的单元最短路径,枚举每条商业线,答案即为 min{ dis1[u] + dis2[v] + w, dis1[v] + dis2[u] + w, dis1[n] }

3. 具体实现

  • 难点在于需要输出路径中的点,因此要记录Dijkstra算法过程中点的松弛顺序
  1. 数组pre1 和 pre2 分别存储以起点和终点为源点的路上各个点的前驱节点
  2. 在Dijkstra算法中,对于每个松弛成功的节点,更新其前驱节点
  3. 由于Dijikstra算法内点的处理顺序为 S -> u/vE -> v/u,但是输出路径时是 S -> u/vv/u -> E,因此两部分的输出方式不同
    1)对于S -> u/v:采用递归方式,从u/v开始寻找前驱节点,直到找到S为止
    2)对于v/u -> E:采用循环,从v/u开始寻找前驱节点,直到找到E为止
  • 在判断是否使用商业线二者路径大小的时候,本来用了一个布尔值 flag 来标识,但是一直TE,改成直接判断路径顶点是否更新就可以了,以后也要注意不能多此一举。

4. 代码

#include <iostream>
#include <string.h>
#include <queue>
using namespace std;
#define max 100000000

struct Edge
{
    int vertex, weight, next;
    
}edges[2222];

int N, S, E, M, K, tot;
int head[555], vis[555], dis1[555], dis2[555], pre1[555], pre2[555];
bool first = true;

void addEdge(int u, int v, int w)
{
    edges[++tot].vertex = v;
    edges[tot].next = head[u];
    edges[tot].weight = w;
    head[u] = tot;
}

void Dijkstra(int s, int *dis, int *pre)
{
    priority_queue<pair<int, int> > q;
    
    for (int i = 0; i <= N; i++)
    {
        dis[i] = max;
        vis[i] = 0;
        pre[i] = 0;
    }
    
    dis[s] = 0;
    pre[s] = -1;
    q.push(make_pair(0, s));
    while (!q.empty())
    {
        int x = q.top().second;
        q.pop();
        if(vis[x]) continue;
        vis[x] = 1;
        for (int i = head[x]; i != -1; i = edges[i].next)
        {
            int y = edges[i].vertex, w = edges[i].weight;
            if (dis[y] > dis[x] + w)
            {
                dis[y] = dis[x] + w;
                pre[y] = x;
                q.push(make_pair(-dis[y], y));
            }
        }
    }
}

void print1(int x)
{
    if (x == S)
    {
        printf("%d", x);
        return;
    }
    print1(pre1[x]);
    printf(" %d", x);
}

void print2(int x)
{
    while (x != E)
    {
        printf(" %d", x);
        x = pre2[x];
    }
    printf(" %d", x);
}

int main()
{
    while (cin >> N >> S >> E)
    {
        tot = 0;
        memset(head, -1, sizeof(head));
        cin >> M;
        int X, Y, Z;
        for (int i = 0; i < M; i++)
        {
            cin >> X >> Y >> Z;
            addEdge(X, Y, Z);
            addEdge(Y, X, Z);
        }
        Dijkstra(S, dis1, pre1);
        Dijkstra(E, dis2, pre2);
        
        int min_dis = dis1[E];
        int bus1 = 0, bus2 = 0;
        cin >> K;
        for (int i = 0; i < K; i++)
        {
            cin >> X >> Y >> Z;
            int d1 = dis1[X] + dis2[Y] + Z;
            int d2 = dis1[Y] + dis2[X] + Z;
            if (min_dis > d1)
            {
                min_dis = d1;
                bus1 = X;
                bus2 = Y;
            }
            if (min_dis > d2)
            {
                min_dis = d2;
                bus1 = Y;
                bus2 = X;
            }
        }
        
        if (first) first = false;
        else cout << endl;
        
        if (bus1 == 0)
        {
            print1(E);
            cout << endl << "Ticket Not Used" << endl << min_dis << endl;
        }
        else
        {
            print1(bus1);
            print2(bus2);
            cout << endl << bus1 << endl << min_dis << endl;
        }
    }
    
    return 0;
}

C TT的美梦

1. 题目大意

这一晚,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:
?
?

2. 思路历程

  • 引入SPFA算法
  1. 为了克服Dijkstra算法不能支持存在负环,以及Floyd算法解决单源最短路径过于复杂度的缺点而产生。
  2. 该算法也对每条边进行松弛操作,由于最短路的路径条数小于点的个数 n,因此松弛 n - 1 轮即可
  3. 为避免重复松弛,用一个队列记录成功松弛的点,每次取队首点并松弛其邻接点
  • 这道题存在负边,并只需求单源最短路径,因此选择SPFA算法非常合适。
  • 每次从 q 中取点 u,对其邻接点 v 进行松弛,若 v 可再松弛把它放入 q。
  • 由于要求出负环,在松弛后要最短路的边数进行计数,当边数超过 n - 1 时停止往下松弛(否则会无限),dfs找到环路中的所有点。

3. 具体实现

  • 辅助数组pre,inq,cnt 分别用于存储前驱节点(对于这题好像没啥用),是否已是最短路,当前最短路的边数。
  • 在加边时即以pow((a[B] - a[A]), 3)对 (A, B)的权值赋值
  • 对于已形成负环路径上的点,避免再次松弛

4. 代码

#include <iostream>
#include <algorithm>
#include <cmath>
#include <queue>
using namespace std;
#define max 100000000

struct Edge
{
    int vertex, next, weight;
    
}edges[200022];

int T, N, M, Q, P, a[222], dis[222], pre[222], inq[222], cnt[222];
bool arrive[222];
int tot = 0, head[222];

void addEdge(int u, int v, int w)
{
    edges[++tot].vertex = v;
    edges[tot].next = head[u];
    edges[tot].weight = w;
    head[u] = tot;
}

void init()
{
    for (int i = 1; i <= N; i++)
    {
        head[i] = -1;
        dis[i] = max;
        pre[i] = 0;
        inq[i] = 0;
        cnt[i] = 0;
        arrive[i] = true;
    }
        
}

void dfs(int u)
{
    arrive[u] = false;
    for (int i = head[u]; i != -1; i = edges[i].next)
    {
        int v = edges[i].vertex;
        if (arrive[v]) dfs(v);
    }
}

void SPFA(int s)
{
    queue<int> q;
    while (!q.empty()) q.pop();
    
    dis[s] = 0;
    inq[s] = 1;
    //pre[s] = s;
    q.push(s);
    while (!q.empty())
    {
        int u = q.front();
        q.pop();
        inq[u] = 0;
        if (!arrive[u]) continue;
        for (int i = head[u]; i != -1; i = edges[i].next)
        {
            int v = edges[i].vertex;
            if (!arrive[v]) continue;
            if (dis[v] > dis[u] + edges[i].weight)
            {
                cnt[v] = cnt[u] + 1;
                if (cnt[v] >= N)
                {
                    dfs(v);
                    continue;
                }
                dis[v] = dis[u] + edges[i].weight;
                pre[v] = u;
                if (!inq[v])
                {
                    q.push(v);
                    inq[v] = 1;
                }
            }
        }
    }
}

int main()
{
    cin >> T;
    for (int i = 1; i <= T; i++)
    {
        cin >> N;
        init();
        for (int j = 1; j <= N; j++)
            cin >> a[j];
        
        cin >> M;
        
        int A, B;
        for (int j = 0; j < M; j++)
        {
            cin >> A >> B;
            addEdge(A, B, pow((a[B] - a[A]), 3));
        }
        
        SPFA(1);
        cin >> Q;
        cout << "Case " << i << ":" << endl;
        for (int j = 0; j < Q; j++)
        {
            cin >> P;
            if (!arrive[P] || dis[P] < 3 || dis[P] == max) cout << "?" << endl;
            else cout << dis[P] << endl;
        }
    }
    
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值