2021 年铜陵市青少年编程大赛 部分题解
小学组 — 近似分析 near
题意
给定 \(q\) 个小数 \(f\),对它们分别判断使用「每次四舍五入最后一位」的方法得到的整数,与「直接根据十分位四舍五入」得到的整数是否相同。
\(f\) 的小数点后的位数不超过 \(10^4\),且至少有一位,\(1 ≤ q ≤ 20\),\(0 < f < 10^4\)。
题解
可以使用类似高精度直接模拟的方法暴力做。
当然也可以像 std 一样直接分类讨论:
- 如果小数点后第一位不是 \(4\),答案为
YES
。 - 如果小数点后全部都是 \(4\),答案为
YES
。 - 否则找到小数点后第一个不是 \(4\) 的数字,如果它不超过 \(4\),那么答案为
YES
,否则答案为NO
。
#include <bits/stdc++.h>
int main() {
freopen("near.in", "r", stdin);
freopen("near.out", "w", stdout);
int q, pos; std::cin >> q;
while(q--) {
std::string s; std::cin >> s;
for(pos = 0; s[pos] != '.'; pos++);
if(s[++pos] != '4') std::cout << "YES\n";
else {
while(pos < s.size() && s[pos] == '4') pos++;
std::cout << (pos == s.size() || s[pos] <= '4' ? "YES\n" : "NO\n");
}
}
return 0;
}
初中组 — 随机排列 rand
题意
有一个排列,每时每刻将 \(0 \sim 2^{k} - 1\) 按照从小到大的顺序排列。
接下来 \(m\) 次操作,每次给定两个非负整数 \(p, q\)。表示先将从小到大的二进制第 \(p\) 位的大小关系取反(即这一位上的 \(0<1\) 变成 \(1<0\),反之亦然),再查询排列中第 \(q\) 个数字的十进制表示。
\(1 ≤ k ≤ 64\),\(1 ≤ m ≤ 10^5\)。
题解
用一个 unsigned long long
的变量 \(r\) 表示当前被取反的位置有哪些,初始时 \(r\) 是 \(0\),每次给定 \(p\) 后 \(r \gets r \oplus 2^p\)(\(\oplus\) 表示异或)。
那么,当询问排列中第 \(q\) 个整数时,直接输出 \(r \oplus q\) 即可。
时间复杂度 \(O(m)\)。
#include <bits/stdc++.h>
typedef unsigned long long ull;
int main() {
freopen("rand.in", "r", stdin);
freopen("rand.out", "w", stdout);
int k, m; ull r = 0; scanf("%d %d", &k, &m);
while(m--) {
int p; ull q; scanf("%d %llu", &p, &q);
printf("%llu\n", q ^ (r ^= (1ull << p)));
}
return 0;
}
初中组 — 春色满园 yard
题意
给定一棵包含 \(n\) 个点的树,有一个长度为 \(m\) 的操作序列,包含两种操作:、
1 a b c
表示 \(a \sim b\) 的路径上每个点的点权 \(+c\)。2 a b
表示查询 \(a \sim b\) 的路径上的点的点权和。
但你并不需要做这道题。给定一个参数 \(k\),你每次需要选择操作序列中相邻两个操作进行交换:
- 两个修改操作不能交换。
- 两个查询操作随意交换。
- 一个修改和一个查询操作只能交换至多 \(k\) 次。
最大化查询操作的答案之和。
\(1 ≤ n ≤ 10^3\),\(1 ≤ m ≤ 200\),\(1 ≤ k ≤ 10^9\),\(1 ≤ c ≤ 100\)。
题解
容易发现,如果 \(k\ge m^2\),那我们肯定会把所有修改放在前面,所有查询放在后面。
现在既然 \(k\) 不够用,相当于是把 \(k\) 次操作分配给每个查询操作。例如一个查询操作被分配了 \(x\) 次机会,相当于它的位置往后移动了 \(x\) 个查询操作。
预处理一个数组 \(w(i, j)\),表示第 \(i\) 个查询操作与其后共 \(j\) 个修改操作对答案造成的贡献之和,预处理部分是可以用前缀和做到 \(O(m^2 n)\) 的。
那么使用背包分配即可,用 \(f(i, j)\) 表示前 \(i\) 个查询操作,已经被分配了 \(j\) 次机会,得到的收益(收益可以提前预处理),转移时枚举给当前查询分配几次机会,即 \(f(i, j) = \max\limits_{k} \{ f(i - 1, j - k) + w(i, k) \}\)。
假设总共有 \(q\) 个查询操作,那么答案就是 \(f(q, k) + {\rm original}\),\(\rm original\) 表示原序列不改动时本身的答案。
时间复杂度 \(O(m^2 (n + \min\{k, m^2\}))\)。
#include <bits/stdc++.h>
typedef long long ll;
const int N = 5e3 + 5, M = 205, K = 1e4 + 5;
std::vector<int> G[N];
int n, m, k, opt[M], a[M], b[M], dep[N], fa[N], len[M];
ll c[M], val[M][M], f[M][K], ans, tag[N];
void build(int u) {
dep[u] = dep[fa[u]] + 1;
for(unsigned i = 0; i < G[u].size(); i++) {
int v = G[u][i];
if(v == fa[u]) continue;
fa[v] = u; build(v);
}
}
void modify(int u, int v, ll w) {
if(dep[u] < dep[v]) std::swap(u, v);
while(dep[u] > dep[v]) tag[u] += w, u = fa[u];
while(u != v) tag[u] += w, tag[v] += w, u = fa[u], v = fa[v];
tag[u] += w;
}
ll query(int u, int v) {
if(dep[u] < dep[v]) std::swap(u, v);
ll res = 0;
while(dep[u] > dep[v]) res += tag[u], u = fa[u];
while(u != v) res += tag[u], res += tag[v], u = fa[u], v = fa[v];
return res + tag[u];
}
ll get(int x, int y) {
memset(tag, 0, sizeof(tag));
modify(a[x], b[x], 1);
return c[y] * query(a[y], b[y]);
}
int main() {
freopen("yard.in", "r", stdin);
freopen("yard.out", "w", stdout);
scanf("%d %d %d", &n, &m, &k);
for(int i = 1; i < n; i++) {
int u, v; scanf("%d %d", &u, &v);
G[u].push_back(v); G[v].push_back(u);
}
build(1);
for(int i = 1; i <= m; i++) {
scanf("%d %d %d", &opt[i], &a[i], &b[i]);
if(opt[i] == 1) {
scanf("%lld", &c[i]);
modify(a[i], b[i], c[i]);
} else ans += query(a[i], b[i]);
}
int qry = 0, tot = 0;
for(int i = 1; i <= m; i++) if(opt[i] == 2) {
qry++;
for(int j = i + 1; j <= m; j++) if(opt[j] == 1) {
len[qry]++;
val[qry][len[qry]] = val[qry][len[qry] - 1] + get(i, j);
}
tot += len[qry];
}
k = std::min(tot, k);
for(int i = 1; i <= qry; i++)
for(int j = 0; j <= k; j++)
for(int cur = 0; cur <= len[i] && cur <= j; cur++)
f[i][j] = std::max(f[i][j], f[i - 1][j - cur] + val[i][cur]);
printf("%lld\n", ans + f[qry][k]);
return 0;
}