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)
同时题目本身存在隐藏限制条件(1geq f[i] - f[i-1] geq 0(0 leq i leq maxn)),即(left{egin{matrix}f[i] - f[i-1] geq 0 \f[i-1] - f[i] geq -1end{matrix} ight.),则在建图过程中每个点(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
", 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;
}

H - COURSES

还是二分图匹配模板。

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

int t, p, n, link[310], ans, cnt, head[310];
bool vis[310];
struct Edge {
    int v, next;
} edge[30010];

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

bool hungry(int u) {
    for (int i = head[u]; i; i = edge[i].next) {
        int v = edge[i].v;
        if (!vis[v]) {
            vis[v] = true;
            if (!link[v] || hungry(link[v])) {
                link[v] = u;
                return true;
            }
        }
    }
    return false;
}

int main() {
    scanf("%d", &t);
    while (t--) {
        scanf("%d%d", &p, &n);
        cnt = 0;
        memset(head, 0, sizeof(head));
        memset(link, 0, sizeof(link));
        for (int i = 1, k, x; i <= p; ++i) {
            scanf("%d", &k);
            while (k--) {
                scanf("%d", &x);
                addEdge(i, x);
            }
        }
        ans = 0;
        for (int i = 1; i <= p; ++i) {
            memset(vis, 0, sizeof(vis));
            ans += hungry(i);
        }
        if (ans == p) {
            puts("YES");
        } else {
            puts("NO");
        }
    }
    return 0;
}
原文地址:https://www.cnblogs.com/IzumiSagiri/p/15017303.html