1. 程式人生 > 其它 >2021NUAA暑假集訓 Day4 部分題解

2021NUAA暑假集訓 Day4 部分題解

比賽連結:21.7.15-NUAA暑期集訓
比賽碼:NUAAACM20210715


目錄

A - 熱浪

單源最短路模板。

#include <cstring>
#include <iostream>
#include <queue>
using namespace std;

#define INF 0x3f3f3f3f
#define io_speed_up ios::sync_with_stdio(false), cin.tie(0), cout.tie(0)

int cnt, head[2510], dis[2510], n, m, s, t;
bool vis[2510];
struct Edge {
    int v, w, next;
} edge[12500];

void addEdge(int u, int v, int w) {
    edge[++cnt].v = v;
    edge[cnt].w = w;
    edge[cnt].next = head[u];
    head[u] = cnt;
}

void spfa(int s) {
    queue<int> q;
    memset(vis, 0, sizeof(vis));
    for (int i = 1; i <= n; ++i) {
        dis[i] = INF;
    }
    dis[s] = 0;
    vis[s] = true;
    q.push(s);
    while (!q.empty()) {
        int u = q.front();
        vis[u] = false;
        q.pop();
        for (int i = head[u]; i; i = edge[i].next) {
            int v = edge[i].v;
            if (dis[v] > dis[u] + edge[i].w) {
                dis[v] = dis[u] + edge[i].w;
                if (!vis[v]) {
                    q.push(v);
                    vis[v] = true;
                }
            }
        }
    }
}

int main() {
    io_speed_up;
    cin >> n >> m >> s >> t;
    for (int i = 1, u, v, w; i <= m; ++i) {
        cin >> u >> v >> w;
        addEdge(u, v, w);
        addEdge(v, u, w);
    }
    spfa(s);
    cout << dis[t];
    return 0;
}

B - Silver Cow Party

存兩張圖:
第一張圖正常存邊,用於求從\(x\)返回其他點時的最短路。
第二張圖存第一張圖的反向邊,由於從其他點到\(x\)是多源單終點,存反向邊則可以看作是從\(x\)到其他點,再求一遍最短路。
將各點在兩次最短路里的\(dis\)值相加,更新最大值。

#include <cstring>
#include <iostream>
#include <queue>
using namespace std;

#define INF 0x3f3f3f3f
#define io_speed_up ios::sync_with_stdio(false), cin.tie(0), cout.tie(0)

int cnt[2], head[2][1010], dis[2][1010], n, m, x;
bool vis[1010];
struct Edge {
    int v, w, next;
} edge[2][100010];

void addEdge(int k, int u, int v, int w) {
    edge[k][++cnt[k]].v = v;
    edge[k][cnt[k]].w = w;
    edge[k][cnt[k]].next = head[k][u];
    head[k][u] = cnt[k];
}

void spfa(int s, int k) {
    queue<int> q;
    memset(vis, 0, sizeof(vis));
    for (int i = 1; i <= n; ++i) {
        dis[k][i] = INF;
    }
    dis[k][s] = 0;
    vis[s] = true;
    q.push(s);
    while (!q.empty()) {
        int u = q.front();
        vis[u] = false;
        q.pop();
        for (int i = head[k][u]; i; i = edge[k][i].next) {
            int v = edge[k][i].v;
            if (dis[k][v] > dis[k][u] + edge[k][i].w) {
                dis[k][v] = dis[k][u] + edge[k][i].w;
                if (!vis[v]) {
                    q.push(v);
                    vis[v] = true;
                }
            }
        }
    }
}

int main() {
    io_speed_up;
    cin >> n >> m >> x;
    for (int i = 1, u, v, w; i <= m; ++i) {
        cin >> u >> v >> w;
        addEdge(0, u, v, w);
        addEdge(1, v, u, w);
    }
    spfa(x, 0);
    spfa(x, 1);
    int ans = 0;
    for (int i = 1; i <= n; ++i) {
        ans = max(ans, dis[0][i] + dis[1][i]);
    }
    cout << ans << endl;
    return 0;
}

C - Intervals

該題是一個差分約束系統。
\(f[i]\)表示在\([0,i]\)區間內取了多少個數。
由輸入可以得到多個不等式\(f[b] - f[a - 1] \geq c\),則在建圖過程中點\(a-1\)有一條權值為\(c\)的有向邊到點\(b\)
同時題目本身存在隱藏限制條件\(1\geq f[i] - f[i-1] \geq 0(0 \leq i \leq maxn)\),即\(\left\{\begin{matrix}f[i] - f[i-1] \geq 0 \\f[i-1] - f[i] \geq -1\end{matrix}\right.\),則在建圖過程中每個點\(i-1\)

有一條權值為\(0\)的有向邊到點\(i\),點\(i\)有一條權值為\(-1\)的有向邊到點\(i-1\)
由於區間左端最小可以為\(0\),所以需要將陣列下標加\(1\),防止出現下標為\(-1\)的情況。
建圖完成之後跑最長路即可。

#include <iostream>
#include <queue>
using namespace std;
#define io_speed_up ios::sync_with_stdio(false), cin.tie(0), cout.tie(0)

struct Edge {
    int v, w, next;
} edge[5000010];
int n, a, b, c, cnt, head[50005], dis[50005], maxb;
bool vis[50005];

void addEdge(int u, int v, int w) {
    edge[++cnt].v = v;
    edge[cnt].w = w;
    edge[cnt].next = head[u];
    head[u] = cnt;
}

void spfa(int s) {
    queue<int> q;
    for (int i = 0; i <= maxb + 1; ++i) {
        dis[i] = -1;
    }
    dis[s] = 0;
    vis[s] = true;
    q.push(s);
    while (!q.empty()) {
        int u = q.front();
        q.pop();
        vis[u] = false;
        for (int i = head[u]; i; i = edge[i].next) {
            int v = edge[i].v;
            if (dis[v] < dis[u] + edge[i].w) {
                dis[v] = dis[u] + edge[i].w;
                if (!vis[v]) {
                    q.push(v);
                    vis[v] = true;
                }
            }
        }
    }
}

int main() {
    io_speed_up;
    cin >> n;
    for (int i = 1; i <= n; ++i) {
        cin >> a >> b >> c;
        addEdge(a, b + 1, c);
        maxb = maxb >= b ? maxb : b;
    }
    for (int i = 1; i <= maxb + 1; ++i) {
        addEdge(i - 1, i, 0);
        addEdge(i, i - 1, -1);
    }
    spfa(0);
    cout << dis[maxb + 1] << endl;
    return 0;
}

D - 飛行路線

建立多層圖,最多免費\(k\)次,所以有\(k+1\)層圖,第\(i\)層表示當前已用\(i\)次免費機會,加邊時在各層的都加上同一條邊,一次免費相當於在上下兩層的兩點之間建立一條權值為\(0\)的邊,起點在第\(0\)層,對整個多層圖跑一遍最短路,一個點的最短距離就是其在各層中的最短距離的最小值。

#include <cstdio>
#include <cstring>
#include <iostream>
#include <queue>
using namespace std;
#define io_speed_up ios::sync_with_stdio(false), cin.tie(0), cout.tie(0)
#define INF 0x3f3f3f3f

int head[110010], n, m, k, s, t, cnt;
long long dis[110010];
bool vis[110010];

struct Edge {
    int v, next;
    long long w;
} edge[11000010];

void addEdge(int u, int v, long long w) {
    edge[++cnt].v = v;
    edge[cnt].w = w;
    edge[cnt].next = head[u];
    head[u] = cnt;
}

void spfa(int s) {
    queue<int> q;
    memset(vis, 0, sizeof(vis));
    for (int i = 0; i < k * n + n; ++i) {
        dis[i] = INF;
    }
    dis[s] = 0;
    vis[s] = true;
    q.push(s);
    while (!q.empty()) {
        int u = q.front();
        vis[u] = false;
        q.pop();
        for (int i = head[u]; i; i = edge[i].next) {
            int v = edge[i].v;
            if (dis[v] > dis[u] + edge[i].w) {
                dis[v] = dis[u] + edge[i].w;
                if (!vis[v]) {
                    q.push(v);
                    vis[v] = true;
                }
            }
        }
    }
}

int main() {
    io_speed_up;
    cin >> n >> m >> k >> s >> t;
    for (int i = 1, u, v, w; i <= m; ++i) {
        cin >> u >> v >> w;
        for (int j = 0; j <= k; ++j) {
            int tu = u + n * j, tv = v + n * j;
            if (j != k) {
                addEdge(tu, tv + n, 0);
                addEdge(tv, tu + n, 0);
            }
            addEdge(tu, tv, w);
            addEdge(tv, tu, w);
        }
    }
    spfa(s);
    long long ans = INF;
    for (int i = 0; i <= k; ++i) {
        ans = min(ans, dis[t + n * i]);
    }
    cout << ans;
    return 0;
}

E - Watering Hole

在已有圖中新增一個點,表示地下天然水源,該點與其他各點之間建立一條權值為打井費用的邊,在某一點打井相當於選擇該點與地下水源之間的邊,然後跑最小生成樹即可。

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

int n, fa[310], cnt;

struct Edge {
    int u, v, w;
    bool operator<(const Edge &obj) const { return w < obj.w; }
} edge[45310];

int find(int x) { return fa[x] == x ? x : fa[x] = find(fa[x]); }

int kruscal() {
    int ans = 0;
    for (int i = 0; i <= n; ++i) {
        fa[i] = i;
    }
    int tot = 0;
    for (int i = 1; i <= cnt; ++i) {
        int u = edge[i].u, v = edge[i].v;
        int fau = find(u), fav = find(v);
        if (fau != fav) {
            fa[fau] = fav;
            tot++;
            ans += edge[i].w;
            if (tot == n) {
                return ans;
            }
        }
    }
}

int main() {
    while (scanf("%d", &n) == 1) {
        cnt = 0;
        for (int i = 1; i <= n; ++i) {
            edge[++cnt].u = 0;
            edge[cnt].v = i;
            scanf("%d", &edge[cnt].w);
        }
        for (int i = 1, w; i <= n; ++i) {
            for (int j = 1; j <= n; ++j) {
                scanf("%d", &w);
                if (i < j) {
                    edge[++cnt].u = i;
                    edge[cnt].v = j;
                    edge[cnt].w = w;
                }
            }
        }
        sort(edge + 1, edge + cnt + 1);
        printf("%d\n", kruscal());
    }
    return 0;
}

G - The Perfect Stall

二分圖匹配模板。

#include <cstring>
#include <iostream>
using namespace std;
#define io_speed_up ios::sync_with_stdio(false), cin.tie(0), cout.tie(0)

int n, m, link[210], ans;
bool vis[210], g[210][210];

bool hungry(int now) {
    for (int i = 1; i <= n; i++) {
        if (!vis[i] && g[now][i]) {
            vis[i] = true;
            if (!link[i] || hungry(link[i])) {
                link[i] = now;
                return true;
            }
        }
    }
    return false;
}

int main() {
    io_speed_up;
    while (cin >> n >> m) {
        memset(g, 0, sizeof(g));
        memset(link, 0, sizeof(link));
        for (int i = 1, s, x; i <= n; ++i) {
            cin >> s;
            while (s--) {
                cin >> x;
                g[i][x] = true;
            }
        }
        ans = 0;
        for (int i = 1; i <= n; ++i) {
            memset(vis, 0, sizeof(vis));
            ans += hungry(i);
        }
        cout << ans << endl;
    }
    return 0;
}