忘打周赛了,亏了 30 分钟 /kk
离 AK 最近的一次,然而没有 /kk
A. 最小值
题目链接:AcWing 4624.
最小值
没啥好说的,题目意思都给你写在脸上了。
1 2 3 4 5 6 7 8 9 10 11
| #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.
压缩文件
比较显然的一道贪心。如果每一次都先压缩能够压缩最大空间的文件,则一定压缩的是最小次数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| #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.
最小移动距离
一道小思维题。一开始以为是宽搜,然后寄了。推出正确解法后,发现已经到时间了。我还是太菜了
先献个丑,贴出我周赛提交的代码(
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
| #include <iostream> #include <queue> #include <cstring> using namespace std; const int N = 109; int n, hd[N]; int f[N][N];
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; 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 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; } flag = false; break; } if (flag) { printf("%d", j); return 0; } } puts("-1"); return 0; }
|
一开始想当然地认为 \(t\) 的最大值为
\(n\),然后就写出了如上代码。但是这个结论是显然错误的。反例:

在该图中,\(t\) 应该等于 \(15\),显然大于 \(8\)。
下面推导正确算法。
首先,我们可以先模拟一遍样例,观察规律。

除了样例 \(2\)
无解,其他的都有解。可以发现,样例 \(2\)
的图不仅有环,还有树形结构。这种情况下无解。
但这样分析的话总有些不踏实——要是还有其他情况呢?
图无外乎有三种:树、链、环。所有图都可以由这三种结构组成。分别讨论这三种情况:
一.
树:由输入数据的格式可知,所有结点有且仅有一个出度。如果这个图中存在树,则一定有多个结点指向同一个结点。这种情况下,被指向的那个结点最多只能与指向它的一个结点相连,从而一定不能到达其它与之相连的点。所以,如果一个图中存在树形结构,则这个图不可能符合要求,输出
\(-1\);
二.
链:首先,需要明确的是,这个图不可能是一个单纯的链,因为一条链只能有
\(n-1\) 条边,而输入中要有 \(n\)
条边。所以,这个图中必定至少存在一个树(分叉)或者环。由于是单向边,只要存在一个链,则链的终点一定无法连接到链的起点。所以这个图不可能符合要求,输出
\(-1\);
三. 环:环的情况下,
容易得知,每个点必定可以在有限的次数中互相到达,有解。
综上所述,这个图必须由且仅由若干个环组成。
那么答案是多少呢?显然是这几个环的长度的最小公倍数。值得注意的是,它可能是绕一圈回到自己,也有可能是绕半圈到对面的点上。如果环的点数为偶数,则绕半圈即可;如果是奇数,则需要绕一圈回到自己。
现在,思路已经明确了。现在要想办法实现它。
通过上面的分析,我们发现需要我们实现的功能有两个:
- 判断这张图是否由若干个环组成;
- 统计每个环由多少个点构成。
当然,还有一个最小公倍数,但是这个是较为基础的模板,所以直接忽略不计了。
关于第一个功能,只需要判断每个点是否有且仅有一个入度就行了。
证明:显然成立。如果这个图中的其中一点有两个入度则有两种可能:

情况 \(1\)
在本题中是不可能的,因为每个结点最多只能有 \(1\) 个出度;情况 \(2\) 在前面的分析中已经被否决了。
所以,只需要统计每个点是否只有一个入度即可。
关于第二个功能,容易联想到 这道题,直接用差不多的方法统计一下就行了。
好了,这就是本题的分析过程。应该不能再详细了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| #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; }
|