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;
}
posted @ 2021-09-16 15:18  syksykCCC  阅读(412)  评论(0编辑  收藏  举报