首页 > 编程语言 >PTA 21级数据结构与算法实验4—图论

PTA 21级数据结构与算法实验4—图论

时间:2022-11-14 23:44:06浏览次数:75  
标签:输出 图论 21 int 样例 PTA ++ 输入 dis

目录

7-1 邻接矩阵表示法创建无向图

输入格式:

输入第一行中给出2个整数i(0<i≤10),j(j≥0),分别为图G的顶点数和边数。
输入第二行为顶点的信息,每个顶点只能用一个字符表示。
依次输入j行,每行输入一条边依附的顶点。

输出格式:

依次输出各顶点的度,行末没有最后的空格。

输入样例:

5 7
ABCDE
AB
AD
BC
BE
CD
CE
DE

输出样例:

2 3 3 3 3

代码

#include <bits/stdc++.h>
using namespace std;
const int N = 310;

int d[N];

int main() {
    int n, m;
    cin >> n >> m;
    string s;
    cin >> s;

    char x, y;
    for (int i = 0; i < m; i++) {
        getchar();
        scanf("%c %c", &x, &y);
        d[x]++, d[y]++;

    }

    for (int i = 0; i < n; i++) {
        if (i == n - 1) cout << d[s[i]] << endl;
        else cout << d[s[i]] << " ";
    }

    return 0;
}

7-2 邻接表创建无向图

输入格式:

输入第一行中给出2个整数i(0<i≤10),j(j≥0),分别为图G的顶点数和边数。
输入第二行为顶点的信息,每个顶点只能用一个字符表示。
依次输入j行,每行输入一条边依附的顶点。

输出格式:

依次输出各顶点的度,行末没有最后的空格。

输入样例:

5 7
ABCDE
AB
AD
BC
BE
CD
CE
DE

输出样例:

2 3 3 3 3

代码 : ( 如果想省事, 就直接看度数, 不用建图, 和 7-1 一样 )

#include <bits/stdc++.h>
using namespace std;
const int N = 310;

int d[N];

int main() {
    int n, m;
    cin >> n >> m;
    string s;
    cin >> s;

    char x, y;
    for (int i = 0; i < m; i++) {
        getchar();
        scanf("%c %c", &x, &y);
        d[x]++, d[y]++;

    }

    for (int i = 0; i < n; i++) {
        if (i == n - 1) cout << d[s[i]] << endl;
        else cout << d[s[i]] << " ";
    }

    return 0;
}

7-3 图深度优先遍历

编写程序对给定的有向图(不一定连通)进行深度优先遍历,图中包含n个顶点,编号为0至n-1。本题限定在深度优先遍历过程中,如果同时出现多个待访问的顶点,则优先选择编号最小的一个进行访问,以顶点0为遍历起点。

输入格式:

输入第一行为两个整数n和e,分别表示图的顶点数和边数,其中n不超过20000,e不超过50。接下来e行表示每条边的信息,每行为两个整数a、b,表示该边的端点编号,但各边并非按端点编号顺序排列。

输出格式:

输出为一行整数,每个整数后一个空格,即该有向图的深度优先遍历结点序列。

输入样例1:

3 3
0 1
1 2
0 2

输出样例1:

0 1 2 

输入样例2:

4 4
0 2
0 1
1 2
3 0

输出样例2:

0 1 2 3 

代码 :

#include <bits/stdc++.h>
using namespace std;

const int N = 2e4 + 10;
vector<int> v[N];
bool vis[N];

void dfs(int u) {

    vis[u] = 1;
    cout << u << " ";
    for (int i = 0; i < v[u].size(); i++) {
        int j = v[u][i];
        if (!vis[j]) dfs(j);
    }
}

int main() {
    int n, m;
    cin >> n >> m;
    int x, y;
    for (int i = 0; i < m; i++) {
        cin >> x >> y;
        v[x].push_back(y);
    }
    for (int i = 0; i < n; i++) {
        sort(v[i].begin(), v[i].end());
    }

    for (int i = 0; i < n; i++) {
        if (!vis[i]) dfs(i);
    }

    return 0;
}

7-4 单源最短路径

请编写程序求给定正权有向图的单源最短路径长度。图中包含n个顶点,编号为0至n-1,以顶点0作为源点。

输入格式:

输入第一行为两个正整数n和e,分别表示图的顶点数和边数,其中n不超过20000,e不超过1000。接下来e行表示每条边的信息,每行为3个非负整数a、b、c,其中a和b表示该边的端点编号,c表示权值。各边并非按端点编号顺序排列。

输出格式:

输出为一行整数,为按顶点编号顺序排列的源点0到各顶点的最短路径长度(不含源点到源点),每个整数后一个空格。如源点到某顶点无最短路径,则不输出该条路径长度。

输入样例:

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

输出样例:

1 1 

代码 :

#include<bits/stdc++.h>
using namespace std;

const int N = 2e4 + 10;
typedef pair<int, int> PII;
int h[N], e[N], w[N], ne[N], idx = 0;
int n, m;
bool vis[N];
int dis[N];

void add(int a, int b, int c) {
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}

void dij() {
    memset(dis, 0x3f, sizeof (dis));

    dis[0] = 0;
    priority_queue<PII, vector<PII>, greater<PII>> q;

    q.push({0, 0});

    while (!q.empty()) {
        auto t = q.top();
        q.pop();

        int u = t.second, d = t.first;

        if (vis[u]) continue;
        vis[u] = 1;

        for (int i = h[u]; ~i; i = ne[i]) {
            int j = e[i];
            if (dis[j] > dis[u] + w[i]) {
                dis[j] = dis[u] + w[i];
                q.push({dis[j], j});
            }
        }
    }
}

int main() {

    memset(h, -1, sizeof (h));
    cin >> n >> m;

    for (int i = 0; i < m; i++) {
        int x, y, z;
        cin >> x >> y >> z;
        add(x, y, z);
    }

    dij();

    for (int i = 1; i < n; i++) {
        if (dis[i] == 0x3f3f3f3f) continue;
        printf("%d ", dis[i]);
    }
    return 0;
}

7-5 列出连通集

给定一个有N个顶点和E条边的无向图,请用DFS和BFS分别列出其所有的连通集。假设顶点从0到N−1编号。进行搜索时,假设我们总是从编号最小的顶点出发,按编号递增的顺序访问邻接点。

输入格式:

输入第1行给出2个整数N(0<N≤10)和E,分别是图的顶点数和边数。随后E行,每行给出一条边的两个端点。每行中的数字之间用1空格分隔。

输出格式:

按照"{ v1 v2 ... v**k }"的格式,每行输出一个连通集。先输出DFS的结果,再输出BFS的结果。

输入样例:

8 6
0 7
0 1
2 0
4 1
2 4
3 5

输出样例:

{ 0 1 4 2 7 }
{ 3 5 }
{ 6 }
{ 0 1 2 7 4 }
{ 3 5 }
{ 6 }

代码 :

#include <bits/stdc++.h>
using namespace std;

vector<int> e[20];
bool vis[20];

void dfs(int u) {
    vis[u] = 1;

    cout << u << " ";

    for (auto it : e[u]) {
        if (!vis[it]) dfs(it);
    }
}

void bfs(int u) {
    queue<int> Q;
    
    Q.push(u);
    vis[u] = 1;

    while (Q.size()) {
        int v = Q.front();
        Q.pop();

        cout << v << " ";

        for (auto it : e[v]) {
            if (!vis[it]) {
                vis[it] = 1;
                Q.push(it);
            }
        }
    }
}

int main() {
    int n, m;
    cin >> n >> m;

    int x, y;
    for (int i = 0; i < m; i++) {
        cin >> x >> y;
        e[x].push_back(y);
        e[y].push_back(x);
    }

    for (int i = 0; i < n; i++) {
        sort(e[i].begin(), e[i].end());
    }

    for (int i = 0; i < n; i++) {
        if (!vis[i]) {
            cout << "{ ";
            dfs(i);
            cout << "}" << endl;
        }
    }

    memset(vis, 0, sizeof vis);

    for (int i = 0; i < n; i++) {
        if (!vis[i]) {
            cout << "{ ";
            bfs(i);
            cout << "}" << endl;
        }
    }
    return 0;
}

7-6 哈利·波特的考试

哈利·波特要考试了,他需要你的帮助。这门课学的是用魔咒将一种动物变成另一种动物的本事。例如将猫变成老鼠的魔咒是haha,将老鼠变成鱼的魔咒是hehe等等。反方向变化的魔咒就是简单地将原来的魔咒倒过来念,例如ahah可以将老鼠变成猫。另外,如果想把猫变成鱼,可以通过念一个直接魔咒lalala,也可以将猫变老鼠、老鼠变鱼的魔咒连起来念:hahahehe。

现在哈利·波特的手里有一本教材,里面列出了所有的变形魔咒和能变的动物。老师允许他自己带一只动物去考场,要考察他把这只动物变成任意一只指定动物的本事。于是他来问你:带什么动物去可以让最难变的那种动物(即该动物变为哈利·波特自己带去的动物所需要的魔咒最长)需要的魔咒最短?例如:如果只有猫、鼠、鱼,则显然哈利·波特应该带鼠去,因为鼠变成另外两种动物都只需要念4个字符;而如果带猫去,则至少需要念6个字符才能把猫变成鱼;同理,带鱼去也不是最好的选择。

输入格式:

输入说明:输入第1行给出两个正整数N (≤100)和M,其中N是考试涉及的动物总数,M是用于直接变形的魔咒条数。为简单起见,我们将动物按1~N编号。随后M行,每行给出了3个正整数,分别是两种动物的编号、以及它们之间变形需要的魔咒的长度(≤100),数字之间用空格分隔。

输出格式:

输出哈利·波特应该带去考场的动物的编号、以及最长的变形魔咒的长度,中间以空格分隔。如果只带1只动物是不可能完成所有变形要求的,则输出0。如果有若干只动物都可以备选,则输出编号最小的那只。

输入样例:

6 11
3 4 70
1 2 1
5 4 50
2 6 50
5 6 60
1 3 70
4 6 60
3 6 80
5 1 100
2 4 60
5 2 80

输出样例:

4 70

代码 :

#include <bits/stdc++.h>
using namespace std;
const int N = 110;
int n, m;
int mp[N][N];
int dis[N];
const int inf = 0x3f3f3f3f;

void floyd() {
    for (int k = 1; k <= n; k++) {
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= n; j++) {
                mp[i][j] = min(dis[i][j], dis[i][k] + dis[k][j]);
            }
        }
    }
}

int main() {
    cin >> n >> m;

    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= n; j++) {
            if (i == j) mp[i][j] = 0;
            else mp[i][j] = inf;
        }
    }

    for (int i = 1; i <= m; i++) {
        int u, v, w;
        cin >> u >> v >> w;
        mp[u][v] = mp[v][u] = w;
    }

    floyd();

    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= n; j++) {
            if (dis[i] < mp[i][j] && i != j) dis[i] = mp[i][j];
        }
    }

    int Min = inf;
    int num;

    for (int i = 1; i <= n; i++) {
        if (Min > dis[i]) {
            Min = dis[i];
            num = i;
        }
    }
    if (Min == inf) cout << "0" << endl;
    else cout << num << " " << Min << endl;

    return 0;
}

7-7 家庭房产

给定每个人的家庭成员和其自己名下的房产,请你统计出每个家庭的人口数、人均房产面积及房产套数。

输入格式:

输入第一行给出一个正整数N(≤1000),随后N行,每行按下列格式给出一个人的房产:

编号 父 母 k 孩子1 ... 孩子k 房产套数 总面积

其中编号是每个人独有的一个4位数的编号;分别是该编号对应的这个人的父母的编号(如果已经过世,则显示-1);k(0≤k≤5)是该人的子女的个数;孩子i是其子女的编号。

输出格式:

首先在第一行输出家庭个数(所有有亲属关系的人都属于同一个家庭)。随后按下列格式输出每个家庭的信息:

家庭成员的最小编号 家庭人口数 人均房产套数 人均房产面积

其中人均值要求保留小数点后3位。家庭信息首先按人均面积降序输出,若有并列,则按成员编号的升序输出。

输入样例:

10
6666 5551 5552 1 7777 1 100
1234 5678 9012 1 0002 2 300
8888 -1 -1 0 1 1000
2468 0001 0004 1 2222 1 500
7777 6666 -1 0 2 300
3721 -1 -1 1 2333 2 150
9012 -1 -1 3 1236 1235 1234 1 100
1235 5678 9012 0 1 50
2222 1236 2468 2 6661 6662 1 300
2333 -1 3721 3 6661 6662 6663 1 100

输出样例:

3
8888 1 1.000 1000.000
0001 15 0.600 100.000
5551 4 0.750 100.000

代码 :

#include <bits/stdc++.h>
using namespace std;

const int N = 1e5 + 10;
int f[N];

struct node {
    int num, s;
} p[N];
struct Node {
    int Min, num;
    double sum, s;
};
map<int, Node> d;
map<int, int> v;
vector<Node> ans;

int Find(int x) {
    if (f[x] != x) f[x] = Find(f[x]);
    return f[x];
}

void F(int x, int y) {
    int xx = Find(x), yy = Find(y);
    if (xx != yy) {
        f[xx] = yy;
    }
}

bool cmp(Node a, Node b) {
    if (a.s != b.s) return a.s > b.s;
    return a.Min < b.Min;
}

int main() {
    int n;
    cin >> n;
    for (int i = 1; i < 10000; i++) f[i] = i;
    int x, y, k;
    int num, s;
    while (n--) {
        cin >> x;
        v[x] = 1;
        for (int i = 0; i < 2; i++) {
            cin >> y;
            if (y == -1) continue;
            v[y] = 1;
            F(x, y);
        }
        cin >> k;
        for (int i = 0; i < k; i++) {
            cin >> y;
            v[y] = 1;
            F(x, y);
        }
        cin >> num >> s;
        p[x].num = num;
        p[x].s = s;
    }

    int res = 0;
    for (auto it : v) d[Find(it.first)].Min = N;
    for (auto it : v) {
        x = Find(it.first);
        if (it.first == x) res++;
        d[x].Min = min(d[x].Min, it.first);
        d[x].num++;
        d[x].sum += p[it.first].num;
        d[x].s += p[it.first].s;
    }

    for (auto it : d) {
        ans.push_back(it.second);
        ans.back().s = ans.back().s / ans.back().num;
        ans.back().sum = ans.back().sum / ans.back().num;
    }

    sort(ans.begin(), ans.end(), cmp);
    cout << res << endl;

    for (int i = 0; i < ans.size(); i++) {
        printf("%04d %d %.3lf %.3lf\n", ans[i].Min, ans[i].num, ans[i].sum,
               ans[i].s);
    }
    return 0;
}

7-8 森森美图

森森最近想让自己的朋友圈熠熠生辉,所以他决定自己写个美化照片的软件,并起名为森森美图。众所周知,在合照中美化自己的面部而不美化合照者的面部是让自己占据朋友圈高点的绝好方法,因此森森美图里当然得有这个功能。 这个功能的第一步是将自己的面部选中。森森首先计算出了一个图像中所有像素点与周围点的相似程度的分数,分数越低表示某个像素点越“像”一个轮廓边缘上的点。 森森认为,任意连续像素点的得分之和越低,表示它们组成的曲线和轮廓边缘的重合程度越高。为了选择出一个完整的面部,森森决定让用户选择面部上的两个像素点A和B,则连接这两个点的直线就将图像分为两部分,然后在这两部分中分别寻找一条从A到B且与轮廓重合程度最高的曲线,就可以拼出用户的面部了。 然而森森计算出来得分矩阵后,突然发现自己不知道怎么找到这两条曲线了,你能帮森森当上朋友圈的小王子吗?

为了解题方便,我们做出以下补充说明:

  • 图像的左上角是坐标原点(0,0),我们假设所有像素按矩阵格式排列,其坐标均为非负整数(即横轴向右为正,纵轴向下为正)。
  • 忽略正好位于连接A和B的直线(注意不是线段)上的像素点,即不认为这部分像素点在任何一个划分部分上,因此曲线也不能经过这部分像素点。
  • 曲线是八连通的(即任一像素点可与其周围的8个像素连通),但为了计算准确,某像素连接对角相邻的斜向像素时,得分额外增加两个像素分数和的2倍减一。例如样例中,经过坐标为(3,1)和(4,2)的两个像素点的曲线,其得分应该是这两个像素点的分数和(2+2),再加上额外的(2+2)乘以(2−1),即约为5.66。

输入格式:

输入在第一行给出两个正整数NM(5≤N,M≤100),表示像素得分矩阵的行数和列数。

接下来N行,每行M个不大于1000的非负整数,即为像素点的分值。

最后一行给出用户选择的起始和结束像素点的坐标(Xstar**t,Ystar**t)和(Xen**d,Yen**d)。4个整数用空格分隔。

输出格式:

在一行中输出划分图片后找到的轮廓曲线的得分和,保留小数点后两位。注意起点和终点的得分不要重复计算。

输入样例:

6 6
9 0 1 9 9 9
9 9 1 2 2 9
9 9 2 0 2 9
9 9 1 1 2 9
9 9 3 3 1 1
9 9 9 9 9 9
2 1 5 4

输出样例:

27.04

代码 :

#include <bits/stdc++.h>
using namespace std;
typedef pair<int, int> PII;

const int N = 110, inf = 0x3f3f3f3f;
double g[N][N], dis[N][N];
bool vis[N][N];
int n, m;
double ans = 0;
int mov[10][2] = {-1, 0, 0, -1, 1, 0, 0, 1, -1, -1, 1, -1, 1, 1, -1, 1};

int check(PII a, PII b, PII p) {
    return (b.first - a.first) * (p.second - a.second) -
           (b.second - a.second) * (p.first - a.first);
}

void bfs(int x1, int y1, int x2, int y2) {
    memset(vis, 0, sizeof(vis));

    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) dis[i][j] = inf;
    }

    PII a = {x1, y1}, b = {x2, y2};
    queue<PII> q;
    q.push({x1, y1});
    dis[x1][y1] = g[x1][y1];

    while (!q.empty()) {
        int dx = q.front().first, dy = q.front().second;
        q.pop();

        vis[dx][dy] = 0;

        for (int i = 0; i < 8; i++) {
            int x = dx + mov[i][0], y = dy + mov[i][1];

            if (x < 0 || x >= n || y < 0 || y >= m) continue;
            PII p = {x, y};

            if (check(a, b, p) > 0 || (x == x2 && y == y2)) {
                double w = g[x][y];

                if (i >= 4) w += (g[x][y] + g[dx][dy]) * (sqrt(2) - 1);

                if (dis[x][y] > dis[dx][dy] + w) {
                    dis[x][y] = dis[dx][dy] + w;
                    if (!vis[x][y]) {
                        q.push({x, y});
                        vis[x][y] = 1;
                    }
                }
            }
        }
    }
    ans += dis[x2][y2];
}

int main() {
    cin >> n >> m;

    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            scanf("%lf", &g[i][j]);
        }
    }
    int x1, y1, x2, y2;
    cin >> y1 >> x1 >> y2 >> x2;

    ans = -g[x1][y1] - g[x2][y2];

    bfs(x1, y1, x2, y2);
    bfs(x2, y2, x1, y1);

    printf("%.2lf\n", ans);
    return 0;
}

7-9 哥尼斯堡的“七桥问题”

哥尼斯堡是位于普累格河上的一座城市,它包含两个岛屿及连接它们的七座桥,如下图所示。

img

可否走过这样的七座桥,而且每桥只走过一次?瑞士数学家欧拉(Leonhard Euler,1707—1783)最终解决了这个问题,并由此创立了拓扑学。

这个问题如今可以描述为判断欧拉回路是否存在的问题。欧拉回路是指不令笔离开纸面,可画过图中每条边仅一次,且可以回到起点的一条回路。现给定一个无向图,问是否存在欧拉回路?

输入格式:

输入第一行给出两个正整数,分别是节点数N (1≤N≤1000)和边数M;随后的M行对应M条边,每行给出一对正整数,分别是该条边直接连通的两个节点的编号(节点从1到N编号)。

输出格式:

若欧拉回路存在则输出1,否则输出0。

输入样例1:

6 10
1 2
2 3
3 1
4 5
5 6
6 4
1 4
1 6
3 4
3 6

输出样例1:

1

输入样例2:

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

输出样例2:

0

代码 :

#include <bits/stdc++.h>
using namespace std;

const int N = 1e5 + 10;
int f[N];
int d[N];

int Find(int x) {
    if (x != f[x]) f[x] = Find(f[x]);
    return f[x];
}

void F(int x, int y) {
    int xx = Find(x), yy = Find(y);
    if (xx != yy) f[xx] = yy;
}

int main() {
    int n, m;
    cin >> n >> m;
    for (int i = 1; i <= n; i++) f[i] = i;

    int x, y;
    for (int i = 0; i < m; i++) {
        cin >> x >> y;
        F(x, y);
        d[x]++;
        d[y]++;
    }

    x = 0, y = 0;

    for (int i = 1; i <= n; i++) {
        if (f[i] == i) x++;
        if (d[i] & 1) y++;
    }
    
    if (x == 1 && y == 0)
        cout << "1" << endl;
    else
        cout << "0" << endl;

    return 0;
}

7-10 公路村村通

现有村落间道路的统计数据表中,列出了有可能建设成标准公路的若干条道路的成本,求使每个村落都有公路连通所需要的最低成本。

输入格式:

输入数据包括城镇数目正整数N(≤1000)和候选道路数目M(≤3N);随后的M行对应M条道路,每行给出3个正整数,分别是该条道路直接连通的两个城镇的编号以及该道路改建的预算成本。为简单起见,城镇从1到N编号。

输出格式:

输出村村通需要的最低成本。如果输入数据不足以保证畅通,则输出−1,表示需要建设更多公路。

输入样例:

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

输出样例:

12

代码 :

#include <bits/stdc++.h>
using namespace std;

const int N = 1e3 + 10, inf = 0x3f3f3f3f;

int g[N][N];
int n, m;
bool vis[N];
int dis[N]; // dis[i] 代表 i 点到 s 集合的最短距离

int prim() {
    memset(dis, 0x3f, sizeof (dis));

    int sum = 0;

    for (int i = 0; i < n; i++) {
        int t = -1;
        for (int j = 1; j <= n; j++) {
            if (!vis[j] && (t == -1 || dis[t] > dis[j]))
                t = j;
        }

        if (i && dis[t] == inf) return inf;

        if (i) sum += dis[t];   // 因为可能会有自环, 所以要先加上, 再更新
        vis[t] = 1;

        for (int j = 1; j <= n; j++) dis[j] = min(dis[j], g[t][j]);
    }

    return sum;
}

int main() {
    cin >> n >> m;
    memset(g, 0x3f, sizeof (g));

    for (int i = 0; i < m; i++) {
        int x, y, z;
        scanf("%d %d %d", &x, &y, &z);
        g[x][y] = g[y][x] = min(g[x][y], z);
    }

    int ans = prim();

    if (ans == inf) cout << "-1" << endl;
    else cout << ans << endl;
    return 0;
}


7-11 旅游规划

有了一张自驾旅游路线图,你会知道城市间的高速公路长度、以及该公路要收取的过路费。现在需要你写一个程序,帮助前来咨询的游客找一条出发地和目的地之间的最短路径。如果有若干条路径都是最短的,那么需要输出最便宜的一条路径。

输入格式:

输入说明:输入数据的第1行给出4个正整数NMSD,其中N(2≤N≤500)是城市的个数,顺便假设城市的编号为0~(N−1);M是高速公路的条数;S是出发地的城市编号;D是目的地的城市编号。随后的M行中,每行给出一条高速公路的信息,分别是:城市1、城市2、高速公路长度、收费额,中间用空格分开,数字均为整数且不超过500。输入保证解的存在。

输出格式:

在一行里输出路径的长度和收费总额,数字间以空格分隔,输出结尾不能有多余空格。

输入样例:

4 5 0 3
0 1 1 20
1 3 2 30
0 3 4 10
0 2 2 20
2 3 1 20

输出样例:

3 40

代码 :

#include <bits/stdc++.h>
using namespace std;

const int N = 5e5 + 10;

int h[N], e[N], l[N], p[N], ne[N], idx = 0;
int n, m, s, d;
int dis[N], cost[N];
bool vis[N];

void add(int a, int b, int c, int d) {
    e[idx] = b, l[idx] = c, p[idx] = d, ne[idx] = h[a], h[a] = idx++;
}

void dij(int s) {
    memset(dis, 0x3f, sizeof (dis));
    memset(cost, 0x3f, sizeof (cost));

    dis[s] = 0, cost[s] = 0;

    for (int i = 0; i < n; i++) {
        int t = -1;
        for (int j = 0; j < n; j++) {
            if (!vis[j] && (t == -1 || dis[t] > dis[j]))
                t = j;
        }

        vis[t] = 1;

        for (int j = h[t]; ~j; j = ne[j]) {
            int k = e[j];
            
            if (dis[k] > dis[t] + l[j]) {
                dis[k] = dis[t] + l[j];
                cost[k] = cost[t] + p[j];
            } else if (dis[k] == dis[t] + l[j] && cost[k] > cost[t] + p[j]) {
                cost[k] = cost[t] + p[j];
            }
        }
    }
}

int main() {
    cin >> n >> m >> s >> d;
    memset(h, -1, sizeof (h));
    for (int i = 0; i < m; i++) {
        int a, b, c, d;
        cin >> a >> b >> c >> d;
        add(a, b, c, d);
        add(b, a, c, d);
    }

    dij(s);

    cout << dis[d] << " " << cost[d] << endl;
    return 0;
}

7-12 关键活动

假定一个工程项目由一组子任务构成,子任务之间有的可以并行执行,有的必须在完成了其它一些子任务后才能执行。“任务调度”包括一组子任务、以及每个子任务可以执行所依赖的子任务集。

比如完成一个专业的所有课程学习和毕业设计可以看成一个本科生要完成的一项工程,各门课程可以看成是子任务。有些课程可以同时开设,比如英语和C程序设计,它们没有必须先修哪门的约束;有些课程则不可以同时开设,因为它们有先后的依赖关系,比如C程序设计和数据结构两门课,必须先学习前者。

但是需要注意的是,对一组子任务,并不是任意的任务调度都是一个可行的方案。比如方案中存在“子任务A依赖于子任务B,子任务B依赖于子任务C,子任务C又依赖于子任务A”,那么这三个任务哪个都不能先执行,这就是一个不可行的方案。

任务调度问题中,如果还给出了完成每个子任务需要的时间,则我们可以算出完成整个工程需要的最短时间。在这些子任务中,有些任务即使推迟几天完成,也不会影响全局的工期;但是有些任务必须准时完成,否则整个项目的工期就要因此延误,这种任务就叫“关键活动”。

请编写程序判定一个给定的工程项目的任务调度是否可行;如果该调度方案可行,则计算完成整个工程项目需要的最短时间,并输出所有的关键活动。

输入格式:

输入第1行给出两个正整数N(≤100)和M,其中N是任务交接点(即衔接相互依赖的两个子任务的节点,例如:若任务2要在任务1完成后才开始,则两任务之间必有一个交接点)的数量。交接点按1*N*编号,*M*是子任务的数量,依次编号为1M。随后M行,每行给出了3个正整数,分别是该任务开始和完成涉及的交接点编号以及该任务所需的时间,整数间用空格分隔。

输出格式:

如果任务调度不可行,则输出0;否则第1行输出完成整个工程项目需要的时间,第2行开始输出所有关键活动,每个关键活动占一行,按格式“V->W”输出,其中V和W为该任务开始和完成涉及的交接点编号。关键活动输出的顺序规则是:任务开始的交接点编号小者优先,起点编号相同时,与输入时任务的顺序相反。

输入样例:

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

输出样例:

17
1->2
2->4
4->6
6->7

代码 : 参考博客地址

#include <bits/stdc++.h>
using namespace std;

const int N = 110, M = N * 2, inf = 0x3f3f3f3f;
int g[N][N];
int n, m;
int in[N], out[N];
int early[N], late[N];

int EarlyTime() {
    queue<int> q;

    for (int i = 1; i <= n; i++) {
        if (in[i] == 0) q.push(i);
    }

    int cnt = 0;

    while (!q.empty()) {
        cnt++;

        int t = q.front();
        q.pop();

        for (int i = 1; i <= n; i++) {
            if (g[t][i] != inf) {
                in[i] --;
                early[i] = max(early[i], early[t] + g[t][i]);

                if (in[i] == 0) q.push(i);
            }
        }
    }

    if (cnt != n) return -1;

    int res = 0;
    for (int i = 1; i <= n; i++) res = max(res, early[i]);

    return res;
}

void LateTime(int x) {
    queue<int> q;

    for (int i = 1; i <= n; i++) {
        if (out[i] == 0) {
            q.push(i);
            late[i] = x;
        }
    }

    while (!q.empty()) {
        int t = q.front();
        q.pop();

        for (int i = n; i >= 1; i--) {
            if (g[i][t] != inf) {
                out[i] --;
                late[i] = min(late[i], late[t] - g[i][t]);

                if (out[i] == 0) q.push(i);
            }
        }
    }
}

int main() {
    cin >> n >> m;

    memset(g, 0x3f, sizeof (g));
    memset(late, 0x3f, sizeof (late));

    for (int i = 0; i < m; i++) {
        int x, y, z;
        cin >> x >> y >> z;
        g[x][y] = z;
        in[y]++;
        out[x]++;
    }

    int x = EarlyTime();

    if (x == -1) {
        cout << "0" << endl;
        return 0;
    }

    LateTime(x);

    cout << x << endl;
    
    for (int i = 1; i <= n; i++) {
        for (int j = n; j >= 1; j--) {
            if (g[i][j] != inf && late[j] - early[i] == g[i][j])
                cout << i << "->" << j << endl;
        }
    }
    return 0;
}

7-13 任务调度的合理性

假定一个工程项目由一组子任务构成,子任务之间有的可以并行执行,有的必须在完成了其它一些子任务后才能执行。“任务调度”包括一组子任务、以及每个子任务可以执行所依赖的子任务集。

比如完成一个专业的所有课程学习和毕业设计可以看成一个本科生要完成的一项工程,各门课程可以看成是子任务。有些课程可以同时开设,比如英语和C程序设计,它们没有必须先修哪门的约束;有些课程则不可以同时开设,因为它们有先后的依赖关系,比如C程序设计和数据结构两门课,必须先学习前者。

但是需要注意的是,对一组子任务,并不是任意的任务调度都是一个可行的方案。比如方案中存在“子任务A依赖于子任务B,子任务B依赖于子任务C,子任务C又依赖于子任务A”,那么这三个任务哪个都不能先执行,这就是一个不可行的方案。你现在的工作是写程序判定任何一个给定的任务调度是否可行。

输入格式:

输入说明:输入第一行给出子任务数N(≤100),子任务按1~N编号。随后N行,每行给出一个子任务的依赖集合:首先给出依赖集合中的子任务数K,随后给出K个子任务编号,整数之间都用空格分隔。

输出格式:

如果方案可行,则输出1,否则输出0。

输入样例1:

12
0
0
2 1 2
0
1 4
1 5
2 3 6
1 3
2 7 8
1 7
1 10
1 7

输出样例1:

1

输入样例2:

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

输出样例2:

0

代码 :

#include <bits/stdc++.h>
using namespace std;

const int N = 110, M = N * 2;

int h[N], e[M], ne[M], idx = 0;
int n;
int d[N];

void add(int a, int b) {
    e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}

bool topsort() {
    queue<int> q;

    int sum = 0;
    for (int i = 1; i <= n; i++) {
        if (!d[i]) q.push(i), sum++;
    }

    while (!q.empty()) {
        int t = q.front();
        q.pop();

        for (int i = h[t]; ~i; i = ne[i]) {
            int j = e[i];
            d[j]--;
            if (d[j] == 0) q.push(j), sum++;
        }
    }

    return sum == n;
}

int main() {
    memset(h, -1, sizeof (h));
    cin >> n;
    int k;
    for (int i = 1; i <= n; i++) {
        cin >> k;
        d[i] = k;
        while (k--) {
            int x;
            cin >> x;
            add(x, i);
        }
    }

    if (topsort()) cout << "1" << endl;
    else cout << "0" << endl;

    return 0;
}

7-14 最短工期

一个项目由若干个任务组成,任务之间有先后依赖顺序。项目经理需要设置一系列里程碑,在每个里程碑节点处检查任务的完成情况,并启动后续的任务。现给定一个项目中各个任务之间的关系,请你计算出这个项目的最早完工时间。

输入格式:

首先第一行给出两个正整数:项目里程碑的数量 N(≤100)和任务总数 M。这里的里程碑从 0 到 N−1 编号。随后 M 行,每行给出一项任务的描述,格式为“任务起始里程碑 任务结束里程碑 工作时长”,三个数字均为非负整数,以空格分隔。

输出格式:

如果整个项目的安排是合理可行的,在一行中输出最早完工时间;否则输出"Impossible"。

输入样例 1:

9 12
0 1 6
0 2 4
0 3 5
1 4 1
2 4 1
3 5 2
5 4 0
4 6 9
4 7 7
5 7 4
6 8 2
7 8 4

输出样例 1:

18

输入样例 2:

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

输出样例 2:

Impossible

代码 :

#include<bits/stdc++.h>
using namespace std;

const int N = 110, M = N * 2;

int h[N], e[M], w[M], ne[M], idx = 0;
int n, m;
int d[N], dis[N];
bool vis[N];

void add(int a, int b, int c) {
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}

int topsort() {
    queue<int> q;

    for (int i = 0; i < n; i++) {
        if (!d[i]) q.push(i);
    }
    int cnt = 0;
    while (!q.empty()) {
        cnt++;
        int t = q.front();
        q.pop();

        for (int i = h[t]; ~i; i = ne[i]) {
            int j = e[i];
            d[j]--;
            if (d[j] == 0) q.push(j);
            dis[j] = max(dis[j], dis[t] + w[i]);
        }        
    }
    return cnt;
}

int main() {
    memset(h, -1, sizeof (h));

    cin >> n >> m;

    for (int i = 0; i < m; i++) {
        int x, y, z;
        cin >> x >> y >> z;
        add(x, y, z);
        d[y]++;
    }

    int cnt = topsort();

    if (cnt != n) cout << "Impossible" << endl;
    else {
        int ans = 0;
        for (int i = 0; i < n; i++) ans = max(ans, dis[i]);
        cout << ans << endl;
    }

    return 0;
}

7-15 最短路径

给定一个有N个顶点和E条边的无向图,顶点从0到N−1编号。请判断给定的两个顶点之间是否有路径存在。如果存在,给出最短路径长度。
这里定义顶点到自身的最短路径长度为0。
进行搜索时,假设我们总是从编号最小的顶点出发,按编号递增的顺序访问邻接点。

输入格式:

输入第1行给出2个整数N(0<N≤10)和E,分别是图的顶点数和边数。
随后E行,每行给出一条边的两个顶点。每行中的数字之间用1空格分隔。
最后一行给出两个顶点编号i,j(0≤i,j<N),i和j之间用空格分隔。

输出格式:

如果i和j之间存在路径,则输出"The length of the shortest path between i and j is X.",X为最短路径长度,
否则输出"There is no path between i and j."。

输入样例1:

7 6
0 1
2 3
1 4
0 2
1 3
5 6
0 3

输出样例1:

The length of the shortest path between 0 and 3 is 2.

输入样例2:

7 6
0 1
2 3
1 4
0 2
1 3
5 6
0 6

输出样例2:

There is no path between 0 and 6.

代码 :

#include <bits/stdc++.h>
using namespace std;

const int N = 50, inf = 0x3f3f3f3f;
int g[N][N];
int n, m;

void floyd() {
    for (int k = 0; k < n; k++) {
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                g[i][j] = min(g[i][j], g[i][k] + g[k][j]);
            }
        }
    }
}

int main() {
    cin >> n >> m;

    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
            if (i == j) g[i][j] = 0;
            else g[i][j] = inf;
        }
    }

    for (int i = 0; i < m; i++) {
        int x, y;
        cin >> x >> y;
        g[x][y] = g[y][x] = 1;
    }

    floyd();
   
    int x, y;
    cin >> x >> y;

    if (g[x][y] == inf) printf("There is no path between %d and %d.\n", x, y);
    else printf("The length of the shortest path between %d and %d is %d.\n", x, y, g[x][y]);

    return 0;
}

7-16 最短路径算法(Floyd-Warshall)

在带权有向图G中,求G中的任意一对顶点间的最短路径问题,也是十分常见的一种问题。

解决这个问题的一个方法是执行n次迪杰斯特拉算法,这样就可以求出每一对顶点间的最短路径,执行的时间复杂度为O(n3)。
而另一种算法是由弗洛伊德提出的,时间复杂度同样是O(n3),但算法的形式简单很多。

在本题中,读入一个有向图的带权邻接矩阵(即数组表示),建立有向图并使用Floyd算法求出每一对顶点间的最短路径长度。

输入格式:

输入的第一行包含1个正整数n,表示图中共有n个顶点。其中n不超过50。

以后的n行中每行有n个用空格隔开的整数。对于第i行的第j个整数,如果大于0,则表示第i个顶点有指向第j个顶点的有向边,且权值为对应的整数值;如果这个整数为0,则表示没有i指向j的有向边。
当i和j相等的时候,保证对应的整数为0。

输出格式:

共有n行,每行有n个整数,表示源点至每一个顶点的最短路径长度。

如果不存在从源点至相应顶点的路径,输出-1。对于某个顶点到其本身的最短路径长度,输出0。

请在每个整数后输出一个空格,并请注意行尾输出换行。

输入样例:

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

输出样例:

0 3 2 1 
6 0 4 7 
2 5 0 3 
3 6 1 0 

代码 :

#include <bits/stdc++.h>
using namespace std;

const int N = 50, inf = 0x3f3f3f3f;
int g[N][N];
int n;

void floyd() {
    for (int k = 0; k < n; k++) {
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                g[i][j] = min(g[i][j], g[i][k] + g[k][j]);
            }
        }
    }
}

int main() {
    cin >> n;

    int x;
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
            scanf("%d", &x);
            if (i != j && x == 0) g[i][j] = inf;
            else g[i][j] = x;
        }
    }

    floyd();

    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
            if (g[i][j] == inf) printf("-1 ");
            else printf("%d ", g[i][j]);
        }
        printf("\n");
    }

    return 0;
}

标签:输出,图论,21,int,样例,PTA,++,输入,dis
From: https://www.cnblogs.com/oneway10101/p/16890974.html

相关文章

  • hyperworks2021位安装教程
    hyperworks2021位安装教程:1.先使用“百度网盘客户端”下载hw21_EN_x64软件安装包到电脑磁盘英文路径文件夹下,并鼠标右击进行解压缩,然后在文件夹内找到hwDesktop2021.2.exe,......
  • ubuntu的iptables开机自动加载规则文件
    1、写一些规则sudoiptables-AINPUT-ptcp--dport80-jACCEPTsudoiptables-IINPUT-ptcp--dport3306-jREJECT2、保存到/etc/iptables.rules文件中sudo......
  • HDU 2191:悼念512汶川大地震遇难同胞——珍惜现在,感恩生活 (多重背包)
    悼念512汶川大地震遇难同胞——珍惜现在,感恩生活TimeLimit:1000/1000MS(Java/Others)    MemoryLimit:32768/32768K(Java/Others)TotalSubmission(s):2529......
  • 2021年12月04日-2021年12月05日 桐城
    这次回去因为宝宝行里要换宿舍,回去帮宝宝搬家,3号你在行里加班到很晚,因为抽到个不好的宿舍在加上加班你心情很不好就没让我去行里见面了,我想着不行我就帮你租个好点的房子省......
  • 2022年02月25日-2021年02月27日 南京
    这是宝宝第一次来南京,我特地把家里收拾了一下提前买好了烟花,买了烛光晚餐的道具还有红酒,我们一起做饭然后共进晚餐,虽然这次我做的饭菜不咋地,但是我们能在一起二人世界还是......
  • 2022年01月29日-2021年01月30日 桐城
    29号早上的车回来的,中午去我外婆家吃的饭,因逢过年晚上,和宝宝的同事一起吃饭的再小芳酒楼,这次超超也回来了,感觉超超还挺逗的,晚上我们吃完后还去搓了顿麻将,我是真的不会打在......
  • 2022年01月15日-2021年01月16日 合肥
    这两天我们是真的开心,这是我们两第一次外出旅游,去了万达茂的游乐园,玩了大摆锤,过山车。跳楼机是真的不敢玩,太刺激了,这里面宝宝吃了一个很好吃的糖葫芦,看了一场精彩的马术表......
  • 2022年06月04日-2021年06月05日 太湖县
    回宝宝家,这是第一次去宝宝太湖县的家,还是挺温馨的,阿姨去了还给我炖了个老母鸡汤,确实不错什么都不放原始的鸡肉香味,周日中午去汉庭酒店旁边买了一点龙虾带到酒店吃,还可以,路......
  • 2022年05月28日-2021年05月29日 安庆
    这次去安庆是参加宝宝同事婚礼,婚礼现场看一对新人有情人圆满幸福,但我不羡慕因为我有宝宝。还去安庆的一个老店面买了端午节礼物,一些是给宝宝那边的亲戚的一些是给我父母的......
  • 2022年06月23日-2021年06月26日 桐城
    这次是调休一天回来的,时间很长,回桐城和宝宝还有她的同事们一起去吃虾兵蟹将的龙虾,不错,吃完回宝宝寝室掼蛋掼起来,周末很享受啊,中午还跟我爸爸吃了什么肥东老母鸡,一点也不好......