【题解】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
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值