忘打周赛了,亏了 30 分钟 /kk
离 AK 最近的一次,然而没有 /kk
题目链接:AcWing 4624. 最小值
没啥好说的,题目意思都给你写在脸上了。
#include
#include
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;
}
题目链接:AcWing 4625. 压缩文件
比较显然的一道贪心。如果每一次都先压缩能够压缩最大空间的文件,则一定压缩的是最小次数。
#include
#include
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. 这里可以不用结构体,但是我懒得再写一遍了。
题目链接:AcWing 4626. 最小移动距离
一道小思维题。一开始以为是宽搜,然后寄了。推出正确解法后,发现已经到时间了。我还是太菜了
先献个丑,贴出我周赛提交的代码(
#include
#include
#include
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 n−1 条边,而输入中要有 n n n 条边。所以,这个图中必定至少存在一个树(分叉)或者环。由于是单向边,只要存在一个链,则链的终点一定无法连接到链的起点。所以这个图不可能符合要求,输出 − 1 -1 −1;
三. 环:环的情况下, 容易得知,每个点必定可以在有限的次数中互相到达,有解。
综上所述,这个图必须由且仅由若干个环组成。
那么答案是多少呢?显然是这几个环的长度的最小公倍数。值得注意的是,它可能是绕一圈回到自己,也有可能是绕半圈到对面的点上。如果环的点数为偶数,则绕半圈即可;如果是奇数,则需要绕一圈回到自己。
现在,思路已经明确了。现在要想办法实现它。
通过上面的分析,我们发现需要我们实现的功能有两个:
当然,还有一个最小公倍数,但是这个是较为基础的模板,所以直接忽略不计了。
关于第一个功能,只需要判断每个点是否有且仅有一个入度就行了。
证明:~~显然成立。~~如果这个图中的其中一点有两个入度则有两种可能:
情况 1 1 1 在本题中是不可能的,因为每个结点最多只能有 1 1 1 个出度;情况 2 2 2 在前面的分析中已经被否决了。
所以,只需要统计每个点是否只有一个入度即可。
关于第二个功能,容易联想到 这道题,直接用差不多的方法统计一下就行了。
好了,这就是本题的分析过程。应该不能再详细了
#include
#include
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;
}