【题解】AcWing 第72场周赛题解

忘打周赛了,亏了 30 分钟 /kk

离 AK 最近的一次,然而没有 /kk

A. 最小值

题目链接:AcWing 4624. 最小值

没啥好说的,题目意思都给你写在脸上了。

#include <iostream>
#include <algorithm>
int main() {
    int T, a, b;
    scanf("%d", &T);
    while (T--) {
        scanf("%d%d", &a, &b);
        printf("%d\n", std::min({a, b, (a+b)/3}));
    }
    return 0;
}
B. 压缩文件

题目链接:AcWing 4625. 压缩文件

比较显然的一道贪心。如果每一次都先压缩能够压缩最大空间的文件,则一定压缩的是最小次数。

#include <iostream>
#include <algorithm>
const int N = 1e5 + 9;
int n, m, ans;
long long sum;
struct Node {
    int x, y, dif;
} a[N];
int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++) {
        scanf("%d%d", &a[i].x, &a[i].y);
        a[i].dif = a[i].x - a[i].y;
        sum += a[i].x;
    }
    std::sort(a + 1, a + n + 1, [](const Node &A, const Node &B) {
        return A.dif > B.dif;
    });
    if (sum <= m) {
        puts("0");
        return 0;
    }
    for (int i = 1; i <= n; i++) {
        sum -= a[i].dif;
        ans++;
        if (sum <= m) {
            printf("%d", ans);
            return 0;
        }
    }
    puts("-1");
    return 0;
}

PS. 这里可以不用结构体,但是我懒得再写一遍了。

C. 最小移动距离

题目链接:AcWing 4626. 最小移动距离

一道小思维题。一开始以为是宽搜,然后寄了。推出正确解法后,发现已经到时间了。我还是太菜了

先献个丑,贴出我周赛提交的代码(

#include <iostream>
#include <queue>
#include <cstring>
using namespace std;
const int N = 109;
int n, hd[N];
int f[N][N];
// f[i][j] 表示从 i 出发,移动 j 个距离到达
struct Edge {
    int to, nx;
} eg[N];
struct Node {
    int st, ed, stp;
};
void addE(int u, int v, int c) {
    eg[c] = {v, hd[u]}, hd[u] = c;
}
void bfs() {
    queue<Node> que;
    for (int i = 1; i <= n; i++)
        que.push({i, i, 0});
    while (que.size()) {
        Node node = que.front(); que.pop();
        int u = node.ed, steps = node.stp;
        if (steps > n) continue;
        for (int i = hd[u]; i; i = eg[i].nx) {
            int v = eg[i].to;
            // printf("(from %d)%d -> %d\n", node.st, u, v);
            f[node.st][steps+1] = v;
            que.push({node.st, v, steps+1});
        }
    }
}
int main() {
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) {
        int x;
        scanf("%d", &x);
        addE(i, x, i);
    }
    bfs();
    // for (int i = 1; i <= n; i++) {
    //     for (int j = 1; j <= n; j++)
    //         printf("%-08d", f[i][j]);
    //     puts("");
    // }
    for (int j = 1; j <= n; j++) {// 枚举距离
        bool flag = true;
        for (int i = 1; i <= n; i++) {
            int t1 = f[i][j];
            int t2 = f[t1][j];
            // if (t1 and t2 and i==t2) continue;
            if (t1 and t2 and i==t2) {
                // printf("%d %d\n", t1, t2);
                continue;
            }
            // printf("%d %d\n", t1, t2);
            flag = false;
            break;
        }
        if (flag) {
            printf("%d", j);
            return 0;
        }
    }
    puts("-1");
    return 0;
}

一开始想当然地认为 t t t 的最大值为 n n n,然后就写出了如上代码。但是这个结论是显然错误的。反例:

在该图中, t t t 应该等于 15 15 15,显然大于 8 8 8

下面推导正确算法。

首先,我们可以先模拟一遍样例,观察规律。

除了样例 2 2 2 无解,其他的都有解。可以发现,样例 2 2 2 的图不仅有环,还有树形结构。这种情况下无解。

但这样分析的话总有些不踏实——要是还有其他情况呢?

图无外乎有三种:树、链、环。所有图都可以由这三种结构组成。分别讨论这三种情况:

一. 树:由输入数据的格式可知,所有结点有且仅有一个出度。如果这个图中存在树,则一定有多个结点指向同一个结点。这种情况下,被指向的那个结点最多只能与指向它的一个结点相连,从而一定不能到达其它与之相连的点。所以,如果一个图中存在树形结构,则这个图不可能符合要求,输出 − 1 -1 1

二. 链:首先,需要明确的是,这个图不可能是一个单纯的链,因为一条链只能有 n − 1 n-1 n1 条边,而输入中要有 n n n 条边。所以,这个图中必定至少存在一个树(分叉)或者环。由于是单向边,只要存在一个链,则链的终点一定无法连接到链的起点。所以这个图不可能符合要求,输出 − 1 -1 1

三. 环:环的情况下, 容易得知,每个点必定可以在有限的次数中互相到达,有解。

综上所述,这个图必须由且仅由若干个环组成。

那么答案是多少呢?显然是这几个环的长度的最小公倍数。值得注意的是,它可能是绕一圈回到自己,也有可能是绕半圈到对面的点上。如果环的点数为偶数,则绕半圈即可;如果是奇数,则需要绕一圈回到自己。

现在,思路已经明确了。现在要想办法实现它。

通过上面的分析,我们发现需要我们实现的功能有两个:

  1. 判断这张图是否由若干个环组成;
  2. 统计每个环由多少个点构成。

当然,还有一个最小公倍数,但是这个是较为基础的模板,所以直接忽略不计了。

关于第一个功能,只需要判断每个点是否有且仅有一个入度就行了。

证明:~~显然成立。~~如果这个图中的其中一点有两个入度则有两种可能:

情况 1 1 1 在本题中是不可能的,因为每个结点最多只能有 1 1 1 个出度;情况 2 2 2 在前面的分析中已经被否决了。

所以,只需要统计每个点是否只有一个入度即可。

关于第二个功能,容易联想到 这道题,直接用差不多的方法统计一下就行了。

好了,这就是本题的分析过程。应该不能再详细了

#include <iostream>
#include <cstring>
const int N = 109;
int n, ans = 1, cnt[N], ds[N];
int gcd(int a, int b) { return b==0 ? a : gcd(b, a%b); }
int lcm(int a, int b) { return a / gcd(a, b) * b; }
int find(int x) { return ds[x]<0 ? x : (ds[x] = find(ds[x])); }
void merge(int x, int y) {
    x = find(x), y = find(y);
    if (x == y) return ;
    if (ds[x] > ds[y]) std::swap(x, y);
    ds[x] += ds[y], ds[y] = x;
}
int main() {
    memset(ds, -1, sizeof ds);
    scanf("%d", &n);
    for (int i = 1, x; i <= n; i++) {
        scanf("%d", &x);
        merge(x, i);
        cnt[x]++;
        if (cnt[x] > 1) {
            puts("-1");
            return 0;
        }
    }
    for (int i = 1; i <= n; i++)
        if (ds[i] < 0) {
            int sz = -ds[i];
            if ((sz & 1) == 0) sz >>= 1;
            ans = lcm(ans, sz);
        }
    printf("%d", ans);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
06-01
这道题是一道典型的费用限制最短路题目,可以使用 Dijkstra 算法或者 SPFA 算法来解决。 具体思路如下: 1. 首先,我们需要读入输入数据。输入数据中包含了道路的数量、起点和终点,以及每条道路的起点、终点、长度和限制费用。 2. 接着,我们需要使用邻接表或邻接矩阵来存储图的信息。对于每条道路,我们可以将其起点和终点作为一个有向边的起点和终点,长度作为边权,限制费用作为边权的上界。 3. 然后,我们可以使用 Dijkstra 算法或 SPFA 算法求解从起点到终点的最短路径。在这个过程中,我们需要记录到每个点的最小费用和最小长度,以及更新每条边的最小费用和最小长度。 4. 最后,我们输出从起点到终点的最短路径长度即可。 需要注意的是,在使用 Dijkstra 算法或 SPFA 算法时,需要对每个点的最小费用和最小长度进行松弛操作。具体来说,当我们从一个点 u 经过一条边 (u,v) 到达另一个点 v 时,如果新的费用和长度比原来的小,则需要更新到达 v 的最小费用和最小长度,并将 v 加入到优先队列(Dijkstra 算法)或队列(SPFA 算法)中。 此外,还需要注意处理边权为 0 或负数的情况,以及处理无法到达终点的情况。 代码实现可以参考以下样例代码: ```c++ #include <cstdio> #include <cstring> #include <queue> #include <vector> using namespace std; const int MAXN = 1005, MAXM = 20005, INF = 0x3f3f3f3f; int n, m, s, t, cnt; int head[MAXN], dis[MAXN], vis[MAXN]; struct Edge { int v, w, c, nxt; } e[MAXM]; void addEdge(int u, int v, int w, int c) { e[++cnt].v = v, e[cnt].w = w, e[cnt].c = c, e[cnt].nxt = head[u], head[u] = cnt; } void dijkstra() { priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> q; memset(dis, 0x3f, sizeof(dis)); memset(vis, 0, sizeof(vis)); dis[s] = 0; q.push(make_pair(0, s)); while (!q.empty()) { int u = q.top().second; q.pop(); if (vis[u]) continue; vis[u] = 1; for (int i = head[u]; i != -1; i = e[i].nxt) { int v = e[i].v, w = e[i].w, c = e[i].c; if (dis[u] + w < dis[v] && c >= dis[u] + w) { dis[v] = dis[u] + w; q.push(make_pair(dis[v], v)); } } } } int main() { memset(head, -1, sizeof(head)); scanf("%d %d %d %d", &n, &m, &s, &t); for (int i = 1; i <= m; i++) { int u, v, w, c; scanf("%d %d %d %d", &u, &v, &w, &c); addEdge(u, v, w, c); addEdge(v, u, w, c); } dijkstra(); if (dis[t] == INF) printf("-1\n"); else printf("%d\n", dis[t]); return 0; } ```

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值