【PAT】06 图论

第六章 图论

AcWing 1475. 紧急情况

问题描述

分析

  • 本题相当于给定我们一个无向图,让我们求两点之间最短路径的数量。并从这些最短路径中选取一条,使得点权之和最大。

  • 使用数组dist记录从起点到每个点的最短路径,使用数组cnt记录从起点到每个点的最短路径条数,使用数组sum记录从起点到每个点的所有最短路径中点权之和最大值。

代码

  • C++
#include <iostream>
#include <cstring>

using namespace std;

const int N = 510;

int n, m, S, T;
int w[N];  // 每个城市救援队数量
int d[N][N];  // 邻接矩阵
int dist[N], cnt[N], sum[N];  // dist: 最短路径长度; cnt:最短路径数量; sum: 最短路径上救援队的数量
bool st[N];  // 判重数组

void dijkstra() {
    
    memset(dist, 0x3f, sizeof dist);
    dist[S] = 0, cnt[S] = 1, sum[S] = w[S];
    
    for (int i = 0; i < n; i++) {
        int t = -1;
        for (int j = 0; j < n; j++)
            if (!st[j] && (t == -1 || dist[t] > dist[j]))
                t = j;
        
        st[t] = true;
        
        for (int j = 0; j < n; j++) 
            if (dist[j] > dist[t] + d[t][j]) {  // 需要更新到j的最短路径
                dist[j] = dist[t] + d[t][j];
                cnt[j] = cnt[t];
                sum[j] = sum[t] + w[j];
            } else if (dist[j] == dist[t] + d[t][j]) {  // 存在多条到j的最短路径
                cnt[j] += cnt[t];
                sum[j] = max(sum[j], sum[t] + w[j]);
            }
    }
}

int main() {
    
    cin >> n >> m >> S >> T;
    for (int i = 0; i < n; i++) cin >> w[i];
    
    memset(d, 0x3f, sizeof d);
    for (int i = 0; i < m; i++) {
        int a, b, c;
        cin >> a >> b >> c;
        d[a][b] = d[b][a] = min(d[a][b], c);
    }
    
    dijkstra();
    
    cout << cnt[T] << ' ' << sum[T] << endl;
    
    return 0;
}

AcWing 1507. 旅行计划

问题描述

分析

  • 本题相当于给定我们一个无向图,让我们求两点之间最短路径(长度之和),如果存在多条最短路径,输出花费(边权之和)最小的一个。AcWing 1475. 紧急情况是输出点权最小的一个。

  • 使用数组dist记录从起点到每个点的最短路径,使用数组cost记录从起点到每个点的所有最短路径花费(边权之和)最小值。

代码

  • C++
#include <iostream>
#include <cstring>
#include <vector>

using namespace std;

const int N = 510;

int n, m, S, T;
int d[N][N], c[N][N];  // 距离、花费
int dist[N], cost[N], pre[N];  // pre记录路径
bool st[N];

void dijkstra() {
    
    memset(dist, 0x3f, sizeof dist);
    memset(cost, 0x3f, sizeof cost);
    dist[S] = 0, cost[S] = 0;
    
    for (int i = 0; i < n; i++) {
        int t = -1;
        for (int j = 0; j < n; j++)
            if (!st[j] && (t == -1 || dist[t] > dist[j]))
                t = j;
        
        st[t] = true;
        
        for (int j = 0; j < n; j++)
            if (dist[j] > dist[t] + d[t][j]) {
                dist[j] = dist[t] + d[t][j];
                cost[j] = cost[t] + c[t][j];
                pre[j] = t;
            } else if (dist[j] == dist[t] + d[t][j] && cost[j] > cost[t] + c[t][j]) {
                cost[j] = cost[t] + c[t][j];
                pre[j] = t;
            }
    }
}

int main() {
    
    cin >> n >> m >> S >> T;
    memset(d, 0x3f, sizeof d);
    memset(c, 0x3f, sizeof c);
    for (int i = 0; i < m; i++) {
        int a, b, x, y;
        cin >> a >> b >> x >> y;
        // 因为要确保存储的是同一条道路的信息,优先满足长度更小
        // 这是因为要求长度之和最小的,如有相同再考虑花费
        if (x < d[a][b]) {
            d[a][b] = d[b][a] = x;
            c[a][b] = c[b][a] = y;
        } else if (x == d[a][b] && y < c[a][b]) {
            d[a][b] = d[b][a] = x;
            c[a][b] = c[b][a] = y;
        }
    }
    
    dijkstra();
    
    vector<int> path;
    for (int i = T; i != S; i = pre[i]) path.push_back(i);
    
    cout << S;
    for (int i = path.size() - 1; i >= 0; i--) cout << ' ' << path[i];
    
    cout << ' ' << dist[T] << ' ' << cost[T] << endl;
    
    return 0;
}

AcWing 1518. 团伙头目

问题描述

分析

  • 本题相当于给定一个无向图,图中可能有多个连通块,请找出各个连通块,如果连通块中点数大于等于三个,并且边权和大于给定阈值K,输出该联通块中 点权最大的节点以及点的数目。

  • 考虑数据的存储,因为点用字符串表示,因此可以使用哈希表存储图。

  • dfs过程中求出每个连通块,以及连通块中边权之和,因为存储图的时候使用两条有向边表示一条无向边,因此最终求出的边权之和需要除以2

代码

  • C++
#include <iostream>
#include <vector>
#include <algorithm>
#include <unordered_map>

using namespace std;

typedef pair<string, int> PSI;

int n, k;
unordered_map<string, vector<PSI>> g;  // 存储图, 可以有重边, 重边边权也要计算
unordered_map<string, int> total;  // 记录每个人的通话时间
unordered_map<string, bool> st;  // 判重数组

int dfs(string ver, vector<string> &nodes) {
    nodes.push_back(ver);
    st[ver] = true;
    
    int sum = 0;
    for (auto edge : g[ver]) {
        sum += edge.second;
        string cur = edge.first;
        if (!st[cur]) sum += dfs(cur, nodes);
    }
    return sum;
}

int main() {
    
    cin >> n >> k;
    for (int i = 0; i < n; i++) {
        string a, b;
        int t;
        cin >> a >> b >> t;
        
        g[a].push_back({b, t});
        g[b].push_back({a, t});
        
        total[a] += t;
        total[b] += t;
    }
    
    vector<PSI> res;  // (帮派老大, 帮派人数)
    for (auto &item : total) {
        string ver = item.first;
        if (st[ver]) continue;  // 说明此人已经在一个帮派中
        
        vector<string> nodes;  // 记录帮派中的人
        int sum = dfs(ver, nodes) / 2;
        
        if (nodes.size() > 2 && sum > k) {
            string boss = nodes[0];
            for (auto node : nodes)
                if (total[boss] < total[node])
                    boss = node;
            res.push_back({boss, (int)nodes.size()});
        }
    }
    
    sort(res.begin(), res.end());
    
    cout << res.size() << endl;
    for (auto item : res) cout << item.first << ' ' << item.second << endl;
    
    return 0;
}

AcWing 1577. 条条大路通罗马

问题描述

分析

  • 本题为了方便操作,将字符串映射成数字,使用哈希表mp进行映射,这样就可以使用邻接矩阵存储图了。

  • 因为当最大幸福感的路线不唯一,我们需要选择平均幸福感最大的路线,可以转化为选择经过节点更少的路径。

代码

  • C++
#include <iostream>
#include <cstring>
#include <vector>
#include <unordered_map>

using namespace std;

const int N = 210;

int n, m;
int w[N];  // 幸福感
int d[N][N];  // 邻接矩阵存储图
// 最短距离,最短路数量,最大点权(幸福感),最小点数, 最短路径的前一个点
int dist[N], cnt[N], cost[N], sum[N], pre[N];
bool st[N];

string city[N];  // 存储城市名称
unordered_map<string, int> mp;  // 城市名->数字

void dijkstra() {
    
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0, cnt[1] = 1;
    
    for (int i = 0; i < n; i++) {
        int t = -1;
        for (int j = 1; j <= n; j++)
            if (!st[j] && (t == -1 || dist[t] > dist[j]))
                t = j;
                
        st[t] = true;
        
        for (int j = 1; j <= n; j++) {
            if (dist[j] > dist[t] + d[t][j]) {
                dist[j] = dist[t] + d[t][j];
                cnt[j] = cnt[t];
                cost[j] = cost[t] + w[j];
                sum[j] = sum[t] + 1;
                pre[j] = t;
            } else if (dist[j] == dist[t] + d[t][j]) {
                // 最短路径不唯一,选取使人们获得最大幸福感的路线
                cnt[j] += cnt[t];  // 最短路径条数
                if (cost[j] < cost[t] + w[j]) {  // 这里是求最大幸福感,注意不等号方向
                    cost[j] = cost[t] + w[j];
                    sum[j] = sum[t] + 1;
                    pre[j] = t;
                } else if (cost[j] == cost[t] + w[j]) {
                    // 最大幸福感的路线不唯一, 选择平均幸福感最大的路线
                    // 即经过的点数较少的路径
                    if (sum[j] > sum[t] + 1) {
                        sum[j] = sum[t] + 1;
                        pre[j] = t;
                    }
                }
            }
        }
    }
}

int main() {
    
    cin >> n >> m >> city[1];
    mp[city[1]] = 1;
    for (int i = 2; i <= n; i++) {
        cin >> city[i] >> w[i];
        mp[city[i]] = i;
    }
    
    memset(d, 0x3f, sizeof d);
    for (int i = 0; i < m; i++) {
        string x, y;
        int c;
        cin >> x >> y >> c;
        int a = mp[x], b = mp[y];
        d[a][b] = d[b][a] = min(d[a][b], c);
    }
    
    dijkstra();
    
    int T = mp["ROM"];
    // 最小成本的路线数量,最小成本,幸福感,平均幸福感(只取整数部分)
    cout << cnt[T] << ' ' << dist[T] << ' ' << cost[T] << ' ' << cost[T] / sum[T] << endl;
    
    vector<int> path;
    for (int i = T; i != 1; i = pre[i]) path.push_back(i);
    
    cout << city[1];
    for (int i = path.size() - 1; i >= 0; i--)
        cout << "->" << city[path[i]];
    cout << endl;
    
    return 0;
}

AcWing 1601. 在线地图

问题描述

分析

  • 按照常理,每条路只有一条记录,因此不需要考虑讨论中的情况。

  • 使用dijkstra求解,如果有多条最短路径,则考虑用时或者经过的路口数即可。

代码

  • C++
#include <iostream>
#include <cstring>
#include <vector>

using namespace std;

const int N = 510;

int n, m, S, T;
// 1: 路程最短的路线; 2: 用时最快的路线
int d1[N][N], d2[N][N];  // 按照常理,每条路只有一条记录
int dist1[N], dist2[N], pre[N];
bool st[N];

pair<int, string> dijkstra(int d1[][N], int d2[][N], int type) {
    
    memset(dist1, 0x3f, sizeof dist1);
    memset(dist2, 0x3f, sizeof dist2);
    memset(st, 0, sizeof st);
    
    dist1[S] = 0, dist2[S] = 0;
    for (int i = 0; i < n; i++) {
        int t = -1;
        for (int j = 0; j < n; j++)
            if (!st[j] && (t == -1 || dist1[t] > dist1[j]))
                t = j;
        
        st[t] = true;
        
        for (int j = 0; j < n; j++) {
            // 线不唯一时考虑的内容
            // 路程最短的路线: 考虑用时最短
            // 用时最快的路线: 考虑经过路口最少
            int w;
            if (type == 0) w = d2[t][j];
            else w = 1;
            
            if (dist1[j] > dist1[t] + d1[t][j]) {
                dist1[j] = dist1[t] + d1[t][j];
                dist2[j] = dist2[t] + w;
                pre[j] = t;
            } else if (dist1[j] == dist1[t] + d1[t][j]) {
                if (dist2[j] > dist2[t] + w) {
                    dist2[j] = dist2[t] + w;
                    pre[j] = t;
                }
            }
        }
    }
    
    vector<int> path;
    for (int i = T; i != S; i = pre[i]) path.push_back(i);
    
    pair<int, string> res;
    res.first = dist1[T];
    res.second = to_string(S);
    for (int i = path.size() - 1; i >= 0; i--)
        res.second += " -> " + to_string(path[i]);
    
    return res;
}

int main() {
    
    cin >> n >> m;
    
    memset(d1, 0x3f, sizeof d1);
    memset(d2, 0x3f, sizeof d2);
    
    for (int i = 0; i < m; i++) {
        int a, b, t, c, d;
        cin >> a >> b >> t >> c >> d;
        if (d1[a][b] > c) d1[a][b] = c, d2[a][b] = d;
        else if (d1[a][b] == c && d2[a][b] > d) d2[a][b] = d;
        if (!t) {
            if (d1[b][a] > c) d1[b][a] = c, d2[b][a] = d;
            else if (d1[b][a] == c && d2[b][a] > d) d2[b][a] = d;
        }
    }
    
    cin >> S >> T;
    
    auto A = dijkstra(d1, d2, 0);  // 路程最短的路线
    auto B = dijkstra(d2, d1, 1);  // 用时最快的路线
    
    if (A.second != B.second) {
        printf("Distance = %d: %s\n", A.first, A.second.c_str());
        printf("Time = %d: %s\n", B.first, B.second.c_str());
    } else {
        printf("Distance = %d; Time = %d: %s\n", A.first, B.first, A.second.c_str());
    }
    
    return 0;
}

AcWing 1615. 哈密顿回路

问题描述

分析

  • 一个图是欧拉回路需要满足如下条件:

    (1)起点和终点相同;

    (2)每一步都有边;

    (3)所有点都走到了;

    (4)一共n+1个点。

代码

  • C++
#include <iostream>
#include <cstring>

using namespace std;

const int N = 210;

int n, m;
bool g[N][N];
bool st[N];
int nodes[N * 2];

bool check(int cnt) {
    
    // 检查条件(1)、(4)
    if (nodes[0] != nodes[cnt - 1] || cnt != n + 1) return false;
    
    // 检查条件(2)
    memset(st, 0, sizeof st);
    for (int i = 0; i < cnt - 1; i++) {
        st[nodes[i]] = true;
        if (!g[nodes[i]][nodes[i + 1]])
            return false;
    }
    
    // 检查条件(2)
    for (int i = 1; i <= n; i++)
        if (!st[i])
            return false;
    
    return true;
}

int main() {
    
    cin >> n >> m;
    
    while (m--) {
        int a, b;
        cin >> a >> b;
        g[a][b] = g[b][a] = true;
    }
    
    int k;
    cin >> k;
    while (k--) {
        int cnt;
        cin >> cnt;
        for (int i = 0; i < cnt; i++) cin >> nodes[i];
        
        if (check(cnt)) puts("YES");
        else puts("NO");
    }
    
    return 0;
}

AcWing 1619. 欧拉路径

问题描述

分析

  • 按照题目的要求判断即可。

代码

  • C++
#include <iostream>

using namespace std;

const int N = 510;

int n, m;
bool g[N][N];
bool st[N];
int d[N];

int dfs(int u) {
    
    st[u] = true;
    
    int res = 1;
    for (int i = 1; i <= n; i++)
        if (!st[i] && g[u][i])
            res += dfs(i);
    return res;
}

int main() {
    
    cin >> n >> m;
    for (int i = 0; i < m; i++) {
        int a, b;
        cin >> a >> b;
        g[a][b] = g[b][a] = true;
        d[a]++, d[b]++;
    }
    
    int cnt = dfs(1);
    
    cout << d[1];
    for (int i = 2; i <= n; i++) cout << ' ' << d[i];
    cout << endl;
    if (cnt == n) {
        int s = 0;
        for (int i = 1; i <= n; i++)
            if (d[i] % 2)
                s++;
        
        if (s == 0) puts("Eulerian");
        else if (s == 2) puts("Semi-Eulerian");
        else puts("Non-Eulerian");
    } else {  // 说明图不连通
        puts("Non-Eulerian");
    }
    
    return 0;
}

AcWing 1624. 地铁地图

问题描述

分析

  • 为了保证换乘次数最少,我们在建立连边时,不能仅建立相邻可达的站点,这样不方便操作,因此对于每条线路,我们将所有线路上的所有站点之间都连接一条边,边的权重是两站点之间(不含该两站点)的站点数加一。

  • 这样的话,问题就变为了:在所有的最短路中找到边数最少的路线。

  • 本题中最多有100条线路,每条线路最多有100个站台,因此点数最多有一万;一条线路最多有一万条边,因此边数最多一百万条。需要使用堆优化版的dijkstra求解。

  • 另外在输出的时候,还需要输出线路,因此存储边的时候还需要存储边所在的线路。

  • 在使用dijkstra求解最短路的时候,除了常规的判重数组st、距离数组dist,还需要经过的边数数组cnt、线路数组pre,以及如何到达该点的信息数组info

  • 另外还要注意如果是环线的话,我们需要选择较短的一条,如下图:

在这里插入图片描述

代码

  • C++
#include <iostream>
#include <queue>
#include <vector>
#include <cstring>

#define x first
#define y second

using namespace std;

typedef pair<int, int> PII;

const int N = 10010, M = 1000010;

int n;  // 线路数
int h[N], e[M], ne[M], w[M], line[M], idx;  // line记录这条边位于哪条线路
int stops[110];  // 存储每个线路站台编号
// 最短距离,最小边数, 最短路径的前一个点
int dist[N], cnt[N], pre[N];
string info[N];  // 到当前点的详细信息
bool st[N];  // dijkstra中判重数组

// 增加一条从站台a到站台b的边,经过c站路,位于地铁id号线
void add(int a, int b, int c, int id) {
    e[idx] = b, w[idx] = c, line[idx] = id, ne[idx] = h[a], h[a] = idx++;
}

string get(int x) {
    string s = to_string(x);
    s = string(4 - s.size(), '0') + s;
    return s;
}

void dijkstra(int start, int end) {
    
    memset(dist, 0x3f, sizeof dist);
    memset(cnt, 0x3f, sizeof cnt);
    memset(st, 0, sizeof st);

    dist[start] = 0;
    cnt[start] = 0;
    
    priority_queue<PII, vector<PII>, greater<PII>> heap;
    heap.push({0, start});
    
    while (heap.size()) {
        
        auto t = heap.top();
        heap.pop();
        
        int ver = t.y;
        if (st[ver]) continue;
        st[ver] = true;
        
        if (ver == end) break;
        
        for (int i = h[ver]; ~i; i = ne[i]) {
            int j = e[i];
            if (dist[j] > dist[ver] + w[i]) {
                dist[j] = dist[ver] + w[i];
                cnt[j] = cnt[ver] + 1;
                pre[j] = ver;
                info[j] = "Take Line#" + to_string(line[i]) + 
                    " from " + get(ver) + " to " + get(j) + ".";
                heap.push({dist[j], j});
            } else if (dist[j] == dist[ver] + w[i]) {  // 存在多条最短
                if (cnt[j] > cnt[ver] + 1) {
                    cnt[j] = cnt[ver] + 1;
                    pre[j] = ver;
                    info[j] = "Take Line#" + to_string(line[i]) + 
                        " from " + get(ver) + " to " + get(j) + ".";
                }
            }
        }
    }
    
    cout << dist[end] << endl;
    vector<string> path;
    for (int i = end; i != start; i = pre[i])
        path.push_back(info[i]);
    for (int i = path.size() - 1; ~i; i--)
        printf("%s\n", path[i].c_str());
}

int main() {
    
    scanf("%d", &n);
    memset(h, -1, sizeof h);
    for (int i = 1; i <= n; i++) {  // 路线编号从1开始
        int m;
        scanf("%d", &m);
        for (int j = 0; j < m; j++) scanf("%d", &stops[j]);
        
        // 同一条线路上的所有站点之间建立连边
        for (int j = 0; j < m; j++)
            for (int k = 0; k < j; k++) {
                int length;
                if (stops[0] != stops[m - 1]) length = j - k;
                else length = min(j - k, m - 1 - j + k);
                
                add(stops[k], stops[j], length, i);
                add(stops[j], stops[k], length, i);
            }
    }
    
    // 处理询问
    int m;
    scanf("%d", &m);
    while (m--) {
        int start, end;
        scanf("%d%d", &start, &end);
        
        dijkstra(start, end);
    }
    
    return 0;
}

AcWing 1627. 顶点覆盖

问题描述

分析

  • 使用结构体存储所有边;对于给定的的顶点集合,使用哈希表存储;然后遍历所有边,检查每条边对应的两个顶点是否至少有一个在哈希表中即可。

  • 如果有某条边两个顶点在哈希表中都不存在,输出No,否则输出Yes

代码

  • C++
#include <iostream>
#include <unordered_set>

using namespace std;

const int N = 10010, M = N;

int n, m;

struct Edge {
    int a, b;
} e[M];

int main() {
    
    ios::sync_with_stdio(false);
    cin.tie(0);
    
    cin >> n >> m;
    for (int i = 0; i < m; i++) cin >> e[i].a >> e[i].b;
    
    int k;
    cin >> k;
    while (k--) {
        int cnt;
        cin >> cnt;
        
        unordered_set<int> S;
        while (cnt--) {
            int x;
            cin >> x;
            S.insert(x);
        }
        
        bool success = true;
        for (int i = 0; i < m; i++) 
            if (S.count(e[i].a) == 0 && S.count(e[i].b) == 0) {
                success = false;
                break;
            }
        if (success) puts("Yes");
        else puts("No");
    }
    
    return 0;
}

AcWing 1632. 第一次接触

问题描述

分析

  • 本题直接暴力枚举C、D即可。注意这里四位编码可能是-0000,因此需要使用字符串。可以将字符串映射到数字上即可。

代码

  • C++
#include <iostream>
#include <vector>
#include <algorithm>
#include <unordered_map>

using namespace std;

const int N = 310;

int n, m;
unordered_map<string, int> mp;  // 人物编号->唯一的id
string num[N];  // 唯一的id -> 人物编号
int id;
bool g[N][N];
vector<int> boys, girls;  // 分别存储男孩、女孩对应的id

int main() {
    
    scanf("%d%d", &n, &m);
    
    char as[10], bs[10];
    for (int i = 0; i < m; i++) {
        scanf("%s%s", as, bs);

        string x = as, y = bs;
        if (x.size() == 5) x = x.substr(1);
        if (y.size() == 5) y = y.substr(1);
        
        if (mp.count(x) == 0) mp[x] = ++id, num[id] = x;
        if (mp.count(y) == 0) mp[y] = ++id, num[id] = y;
        
        int a = mp[x], b = mp[y];
        g[a][b] = g[b][a] = true;
        
        if (as[0] != '-') boys.push_back(a);
        else girls.push_back(a);
        if (bs[0] != '-') boys.push_back(b);
        else girls.push_back(b);
    }

    sort(boys.begin(), boys.end());
    boys.erase(unique(boys.begin(), boys.end()), boys.end());
    sort(girls.begin(), girls.end());
    girls.erase(unique(girls.begin(), girls.end()), girls.end());
    
    int k;
    scanf("%d", &k);
    
    while (k--) {
        scanf("%s%s", as, bs);
        string x = as, y = bs;
        
        vector<pair<string, string>> res;
        
        vector<int> p = boys, q = boys;
        if (x[0] == '-') p = girls, x = x.substr(1);
        if (y[0] == '-') q = girls, y = y.substr(1);
        
        int a = mp[x], b = mp[y];
        
        for (auto c : p)
            for (auto d : q)
                if (c != a && c != b && d != a && d != b && g[a][c] && g[c][d] && g[d][b])
                    res.push_back({num[c], num[d]});
        
        sort(res.begin(), res.end());
        printf("%d\n", res.size());
        for (auto p : res)
            printf("%s %s\n", p.first.c_str(), p.second.c_str());
    }
    
    return 0;
}

AcWing 1635. 最大团

问题描述

在这里插入图片描述

分析

  • 使用邻接矩阵存储图,对于每个读入的顶点集合(存储到vers中),首先检查其中所有的顶点是否全部连通,不是的话说明不是团,否则是团。

  • 接着检查是否为最大团:检查团外的其他顶点 和 团中的每个顶点的连接情况,如果存在某个团外的点和 团中所有点相连,说明不是做大团;否则是最大团。

代码

  • C++
#include <iostream>
#include <cstring>

using namespace std;

const int N = 210;

int n, m;
bool g[N][N];
int vers[N];
bool st[N];

bool check_clique(int cnt) {
    for (int i = 0; i < cnt; i++)
        for (int j = 0; j < i; j++)
            if (!g[vers[i]][vers[j]]) 
                return false;
    return true;
}

bool check_maximal(int cnt) {
    
    memset(st, 0, sizeof st);
    for (int i = 0; i < cnt; i++)
        st[vers[i]] = true;
        
    for (int i = 1; i <= n; i++)
        if (!st[i]) {  // 说明是团外的点
            bool success = true;  // 是否可以加入点i
            for (int j = 0; j < cnt; j++)
                if (!g[i][vers[j]]) {
                    success = false;
                    break;
                }
            if (success) return false;
        }
        
    return true;
}

int main() {
    
    ios::sync_with_stdio(false);
    cin.tie(0);
    
    cin >> n >> m;
    for (int i = 0; i < m; i++) {
        int a, b;
        cin >> a >> b;
        g[a][b] = g[b][a] = true;
    }
    
    int k;
    cin >> k;
    while (k--) {
        int cnt;
        cin >> cnt;
        for (int i = 0; i < cnt; i++) cin >> vers[i];
        
        if (check_clique(cnt)) {
            if (check_maximal(cnt)) puts("Yes");
            else puts("Not Maximal");
        } else 
            puts("Not a Clique");
    }
    
    return 0;
}

AcWing 1639. 拓扑顺序

问题描述

分析

  • 根据给出的拓扑排序的序列,我们记录图中每个点在序列中的位置,记录到数组p中,然后遍历所有的边,对于每条有向边(a, b),必须满足p[a]<p[b],否则不满足拓扑序。

代码

  • C++
#include <iostream>

using namespace std;

const int N = 1010, M = 10010;

int n, m;

struct Edge {
    int a, b;
} e[M];
int p[N];  // 每个点在数组中的位置

int main() {
    
    cin >> n >> m;
    for (int i = 0; i < m; i++) cin >> e[i].a >> e[i].b;
    
    int k;
    cin >> k;
    bool is_first = true;
    for (int i = 0; i < k; i++) {
        for (int j = 0; j < n; j++) {
            int x;
            cin >> x;
            p[x] = j;
        }
        
        bool success = true;
        for (int j = 0; j < m; j++)
            if (p[e[j].a] > p[e[j].b]) {
                success = false;
                break;
            }
        
        if (!success) {
            if (is_first) is_first = false;
            else cout << ' ';
            
            cout << i;
        }
    }
    cout << endl;
    
    return 0;
}

AcWing 1643. 旅行商问题

问题描述

分析

  • 首先判断给出的路径是否可行,即是否存在两个相邻的点不存在边,可以使用sum记录路径长度,如果两个相邻的点不存在边的话,将sum置为-1

  • 如果sum!=-1说明这是一条合法路径,然后判断这是否是一个经过所有点的环,需要满足两个条件:(1)经过所有的点;(2)首尾节点相同。

  • 不满足上面两个条件中的任何一个,说明不是一个经过所有点的环,将success置为false表示。

  • 否则如果满足上述两个条件的话,还需要判断经过的点数是不是恰好为n+1,如果是的话说明是简单路径,否则说明是一个经过了所有点但某些点经过多次的环。

代码

  • C++
#include <iostream>
#include <cstring>

using namespace std;

const int N = 210, INF = 0x3f3f3f3f;

int n, m;
int g[N][N];
bool st[N];  // 路径上是否经过该点
int vers[310];

int main() {
    
    cin >> n >> m;
    memset(g, 0x3f, sizeof g);
    for (int i = 0; i < m; i++) {
        int a, b, c;
        cin >> a >> b >> c;
        g[a][b] = g[b][a] = c;
    }
    
    int k;
    cin >> k;
    
    int min_dist = INF, min_id = 0;
    for (int T = 1; T <= k; T++) {
        int cnt;
        cin >> cnt;
        for (int i = 0; i < cnt; i++) cin >> vers[i];
        
        memset(st, 0, sizeof st);
        
        int sum = 0;
        bool success = true;
        for (int i = 0; i + 1 < cnt; i++) {
            int a = vers[i], b = vers[i + 1];
            if (g[a][b] == INF) {
                sum = -1;
                success = false;
                break;
            }
            sum += g[a][b];
            st[a] = true;
        }
        
        for (int i = 1; i <= n; i++)
            if (!st[i]) {
                success = false;
                break;
            }

        if (vers[0] != vers[cnt - 1]) success = false;
        
        if (sum == -1) printf("Path %d: NA (Not a TS cycle)\n", T);
        else {
            if (!success) printf("Path %d: %d (Not a TS cycle)\n", T, sum);
            else {
                if (cnt == n + 1) printf("Path %d: %d (TS simple cycle)\n", T, sum);
                else printf("Path %d: %d (TS cycle)\n", T, sum);
                
                if (min_dist > sum) {
                    min_dist = sum;
                    min_id = T;
                }
            }
        }
    }
    
    printf("Shortest Dist(%d) = %d\n", min_id, min_dist);
    
    return 0;
}

AcWing 1648. 顶点着色

问题描述

分析

  • 使用结构体将所有边存储下来,然后对于每个询问,遍历所有边,检查是否存在边的两个顶点颜色相同,如果存在则说明不是合法方案。否则是合法方案,遍历所有顶点,统计顶点的颜色种类即可。

  • 统计颜色种类可以使用哈希表。

代码

  • C++
#include <iostream>
#include <unordered_set>

using namespace std;

const int N = 10010, M = N;

int n, m;
int vers[N];

struct Edge {
    int a, b;
} e[M];

int main() {
    
    cin >> n >> m;
    for (int i = 0; i < m; i++) cin >> e[i].a >> e[i].b;
    
    int k;
    cin >> k;
    while (k--) {
        for (int i = 0; i < n; i++) cin >> vers[i];
        
        bool success = true;
        for (int i = 0; i < m; i++)
            if (vers[e[i].a] == vers[e[i].b]) {
                success = false;
                break;
            }
        
        if (!success) puts("No");
        else {
            unordered_set<int> S;
            for (int i = 0; i < n; i++) S.insert(vers[i]);
            printf("%d-coloring\n", S.size());
        }
    }
    
    return 0;
}

AcWing 1495. 公共自行车管理

问题描述

分析

  • 本题点的编号是0~n,其中0是起点,假设终点是S,则我们想要找到起点到终点的一条路径,这条路径满足:

    (1)距离最短;

    (2)如果有多条最短路径,选取带去自行车最少的路线;

    (3)如果仍有多条路径,选择带回自行车最少的路线。

  • 本题无法在dijkstra的过程中确定需要带去多少自行车。假设s1、s2都可以到达s3,且s1、s2分别比理想状态多了3、4量自行车,我们无法确定应该选哪一个,这是因为s3可能缺少4辆自行车,则此时选择s2更好。

  • 我们首先用dijkstra求出终点起点的最短路径,然后暴力枚举所有最短路径,找到一条带去自行车最少的路径(有多条的话,找到带回自行车最少的路径)。

  • 那么我们如何找到带去自行车和带回自行车的数量呢?可以使用一个变量s表示在当前节点要带回的自行车数量,该节点缺少自行车的话s减小(负数说明需要带自行车,整数说明不需要带),s过程中最小的负数就是需要带去的自行车数量,记为mins。则最终需要带去的自行车数量为sd=abs(min(mins, 0)),因为如果每个站台都不缺少自行车,则mins为正数,此时可以不用带去自行车。

  • 需要带回的自行车数量就是:在终点还剩余的自行车数量ssd之和。

代码

  • C++
#include <iostream>
#include <vector>
#include <cstring>

using namespace std;

const int N = 510, INF = 0x3f3f3f3f;

// 每个车站的最大存车量, 车站总数, 终点, 道路数量
int C, n, S, m;
int c[N];
int g[N][N];
int dist[N];  // 终点到起点的最短路径
bool st[N];

vector<int> path, ans;  // path: dfs过程中的路径; ans: 记录最优路径
int send = INF, bring = INF;  // send: 需要带去的行车数量; bring: 需要带回的行车数量

void dijkstra() {
    
    memset(dist, 0x3f, sizeof dist);
    dist[S] = 0;
    
    for (int i = 0; i <= n; i++) {
        int t = -1;
        for (int j = 0; j <= n; j++)
            if (!st[j] && (t == -1 || dist[j] < dist[t]))
                t = j;
        
        st[t] = true;        
        
        for (int j = 0; j <= n; j++)
            dist[j] = min(dist[j], dist[t] + g[t][j]);
    }
}

// u: 当前考察的节点; 
// s: 到达当前节点前需要带回的自行车数量(负数的话说明要从起点带来自行车)
// mins: dfs过程中s的最小值
void dfs(int u, int s, int mins) {
    
    if (u) {  // 起点不用考虑
        s -= (C + 1) / 2 - c[u];
        mins = min(mins, s);
    }
    
    if (u == S) {  // 到达终点
        int sd = abs(min(mins, 0));  // 当前考察路径需要带去的自行车数量
        int bg = s + sd;  // 当前考察路径需要带回的自行车数量
        
        if (sd < send) send = sd, bring = bg, ans = path;
        else if (sd == send && bg < bring) bring = bg, ans = path;
    }
    
    for (int i = 1; i <= n; i++)
        if (dist[u] == g[u][i] + dist[i]) {
            path.push_back(i);
            dfs(i, s, mins);
            path.pop_back();
        }
}

int main() {
    
    cin >> C >> n >> S >> m;
    memset(g, 0x3f, sizeof g);
    for (int i = 1; i <= n; i++) cin >> c[i];
    for (int i = 0; i < m; i++) {
        int a, b, c;
        cin >> a >> b >> c;
        g[a][b] = g[b][a] = min(g[a][b], c);
    }
    
    dijkstra();
    
    // 从起点开始搜,找到带去自行车(带回自行车)最少的路径
    path.push_back(0);
    dfs(0, 0, 0);
    
    cout << send << ' ' << ans[0];
    for (int i = 1; i < ans.size(); i++)
        cout << "->" << ans[i];
    cout << ' ' << bring << endl;
    
    return 0;
}

AcWing 1558. 加油站

问题描述

分析

  • 这里将m个加油站候选位置的编号变为n+1、n+m,这些候选位置之间也可能有路径,因此在求最短路径的时候也需要考虑。

  • 因为一共最多只有10个候选位置,可以枚举加油站设置的位置,然后检查以此候选位置为加油站位置是否符合题目要求,要求如下:

    (1)所有房屋距离加油站最短距离不超过给定值D

    (2)距离加油站最近的房屋的距离的距离mind要尽可能大;

    (3)所有房屋到加油站的距离之和sumd尽可能小;

  • 另外还要求如果满足上述条件的情况下存在多条答案,则选择编号较小的候选位置。我们在枚举候选位置的时候从小到大枚举即可满足此条件。

代码

  • C++
#include <iostream>
#include <cstring>

using namespace std;

const int N = 1020, INF = 0x3f3f3f3f;

int n, m, k, D;
int g[N][N];
int dist[N];
bool st[N];

// 返回位置编号
int get(string s) {
    if (s[0] == 'G') return n + stoi(s.substr(1));
    return stoi(s);
}

void dijkstra(int S, int &mind, int &sumd) {
    
    memset(dist, 0x3f, sizeof dist);
    memset(st, 0, sizeof st);
    
    dist[S] = 0;
    
    for (int i = 0; i < n + m; i++) {
        int t = -1;
        for (int j = 1; j <= n + m; j++)
            if (!st[j] && (t == -1 || dist[j] < dist[t]))
                t = j;
        
        st[t] = true;
        
        for (int j = 1; j <= n + m; j++)
            dist[j] = min(dist[j], dist[t] + g[t][j]);
    }
    
    // 查考所有村庄到当前候选位置的距离
    for (int i = 1; i <= n; i++)
        if (dist[i] > D) {
            mind = -INF;
            return;
        }
    
    mind = INF, sumd = 0;
    for (int i = 1; i <= n; i++) {
        mind = min(mind, dist[i]);
        sumd += dist[i];
    }
}

int main() {
    
    cin >> n >> m >> k >> D;
    
    memset(g, 0x3f, sizeof g);
    for (int i = 0; i < k; i++) {
        string a, b;
        int z;
        cin >> a >> b >> z;
        int x = get(a), y = get(b);
        g[x][y] = g[y][x] = min(g[x][y], z);
    }
    
    // res: 选择的候选点编号; maxd: 最短距离的最大值; sumd: 对应的所有房屋到加油站的距离之和
    int res = -1, mind = 0, sumd = INF;
    for (int i = n + 1; i <= n + m; i++) {
        
        int d1, d2;  // 选择i为加油站位置对应的mind, sumd
        dijkstra(i, d1, d2);
        
        if (d1 > mind) mind = d1, sumd = d2, res = i;
        else if (d1 == mind && d2 < sumd) sumd = d2, res = i;
    }
    
    if (res == -1) puts("No Solution");
    else printf("G%d\n%.1lf %.1lf\n", res - n, (double)mind, (double)sumd / n + 1e-8);
    
    return 0;
}

AcWing 1562. 微博转发

问题描述

分析

  • 如果x关注y,则让yx连接一条边。本题相当于从某个点出发宽度优先遍历一定的层数,将遍历到的点数输出即可。

代码

  • C++
#include <iostream>
#include <cstring>
#include <queue>

using namespace std;

const int N = 1010, M = 100010;

int n, l;
int h[N], e[M], ne[M], idx;
bool st[N];

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

int bfs(int start) {
    
    memset(st, 0, sizeof st);
    queue<int> q;
    q.push(start);
    st[start] = true;
    
    int cnt = 0;
    for (int step = 0; step < l; step++) {
        int sz = q.size();
        cnt += sz;
        
        for (int i = 0; i < sz; i++) {
            int t = q.front();
            q.pop();
            
            for (int j = h[t]; ~j; j = ne[j]) {
                int k = e[j];
                if (!st[k]) {
                    q.push(k);
                    st[k] = true;
                }
            }
        }
    }
    cnt += q.size() - 1;
    
    return cnt;
}

int main() {
    
    scanf("%d%d", &n, &l);
    memset(h, -1, sizeof h);
    for (int i = 1; i <= n; i++) {
        int cnt;
        scanf("%d", &cnt);
        while (cnt--) {
            int id;
            scanf("%d", &id);
            add(id, i);  // i关注id
        }
    }
    
    int k;
    scanf("%d", &k);
    while (k--) {
        int x;
        scanf("%d", &x);
        printf("%d\n", bfs(x));
    }
    
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值