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
";
		else {
			while(pos < s.size() && s[pos] == '4') pos++;
			std::cout << (pos == s.size() || s[pos] <= '4' ? "YES
" : "NO
");
		}
	}
	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
", 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)

题解

容易发现,如果 (kge m^2),那我们肯定会把所有修改放在前面,所有查询放在后面。

现在既然 (k) 不够用,相当于是把 (k) 次操作分配给每个查询操作。例如一个查询操作被分配了 (x) 次机会,相当于它的位置往后移动了 (x) 个查询操作。

预处理一个数组 (w(i, j)),表示第 (i) 个查询操作与其后共 (j) 个修改操作对答案造成的贡献之和,预处理部分是可以用前缀和做到 (O(m^2 n)) 的。

那么使用背包分配即可,用 (f(i, j)) 表示前 (i) 个查询操作,已经被分配了 (j) 次机会,得到的收益(收益可以提前预处理),转移时枚举给当前查询分配几次机会,即 (f(i, j) = maxlimits_{k} { f(i - 1, j - k) + w(i, k) })

假设总共有 (q) 个查询操作,那么答案就是 (f(q, k) + { m original})( m 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
", ans + f[qry][k]);
	return 0;
}
原文地址:https://www.cnblogs.com/syksykCCC/p/TLOI2021.html