目录

AtCoder Beginner Contest 008

A

题意

正整数 \(S\) 到 \(T\) 中有多少个数?\(S \leq T\) 。

题解

第一个思路是,\(T - S\) 是线条图的边的个数,线条图的点数为边数加一,即 \(T - S + 1\) 。

第二个思路是,\(S\) 个数减去 \(T - 1\) 个数,即 \(T\) 到 \(S\) 的数的数量,答案为 \(T - S + 1\) 。

B

题意

给 \(n\) 个字符串,询问出现次数最多的字符。

题解

这类问题和 trie 无关。实际上用了一个经典 trick 。

经典做法:将字符串的双重哈希值放入 C++ 映射表 map 。

  • 可以避免直接将字符串放入 map 导致的比较常数过高问题。
  • 可以节省空间。

最后可以找到出现次数最多的 key (哈希值)。

建另一个映射表维护 key 对应的 string 可查询。

view
const int MAXN = 2E5;
const int HA = 2;
const int PP[HA] = {int(1E9) + 123, int(1E9) + 403};
const int QQ[HA] = {123456, 654321};
i64 pw[HA][MAXN], hsh[HA][MAXN];

void solve() {
	for (int h = 0; h < HA; h++) {
		pw[h][0] = 1;
		for (int i = 1; i < MAXN; i++) {
			pw[h][i] = pw[h][i - 1] * QQ[h] % PP[h];
		}
	}

	int n; std::cin >> n;
	int mx = 0;
	std::array<i64, 2> key;
	std::map<std::array<i64, 2>, int> mp;
	std::map<std::array<i64, 2>, std::string> rec;
	for (int i = 1; i <= n; i++) {
		std::string s; std::cin >> s;
		std::array<i64, 2> ckey;
		for (int h = 0; h < HA; h++) {
			for (int j = 1; j <= s.size(); j++) {
				char c = s[j - 1];
				hsh[h][j] = (hsh[h][j - 1] * QQ[h] % PP[h] + c) % PP[h];
			}
			assert(s.size() > 0);
			ckey[h] = hsh[h][s.size() - 1];
		}
		mp[ckey] += 1;
		if (mx < mp[ckey]) {
			mx = mp[ckey];
			key = ckey;
		}
		if (!rec.count(ckey)) rec[ckey] = s;
	}
	std::cout << rec[key] << "\n";
}

C

题意

给 \(n\) 枚硬币,面值分别为 \(a_1, a_2, a_3, \cdots, a_n\) 。一开始所有硬币随机排列,且正面朝上。
从左到右遍历每一枚硬币,将这枚硬币右边所有其倍数面值的硬币翻转一次。
询问这些硬币朝上的期望个数。

题解

一:考虑统一计算排列内 \(n\) 个硬币的期望。

随机变量可以看成 \(n\) 枚的排列,这些硬币的排列共可能有 \(n!\) 种可能。
设每种排列出现的概率为 \(p_i\) ,这个排列硬币朝上的个数为 \(x_i\) 。
期望计算公式:

\[E(X) = \frac{\sum_{i = 1}^{n!} 1 \times x_i}{n!} = \sum_{i = 1}^{n!} p_i x_i = \sum_{i = 1}^{n!} \frac{1}{n!} x_i \]

从公式可以看出,这种算法中枚举情况的时间复杂度是 \(O(n!)\) ,计算每种情况的期望的时间复杂度是 \(O(n^{2})\) 。总时间复杂度为 \(O(n^{2} n!)\) 。可以通过 \(n \leq 8\) 的数据。
可以使用 \(dfs\) 或 \(next\_permutation\) 完成这个算法。

solution1
	int N; std::cin >> N; assert(N <= 8);
	std::vector<int> a(N + 1);
	for (int i = 1; i <= N; i++) std::cin >> a[i];

	std::vector<int> p(N + 1);
	std::iota(p.begin(), p.end(), 0);
	
	double ans = 0;

	int numerator = 0, denominator = 1;
	for (int i = 1; i <= N; i++) denominator *= i;

	std::vector<int> cnt(N + 1);

	do {
		std::vector<int> bk(N + 1, 1);
		for (int i = 1; i <= N; i++) {
			for (int j = i + 1; j <= N; j++) if (a[p[j]] % a[p[i]] == 0) {
				bk[p[j]] ^= 1;
			}
		}
		
		for (int i = 1; i <= N; i++) numerator += bk[i];
	}
	while (std::next_permutation(p.begin() + 1, p.end()));
	
	ans = 1.L * numerator / denominator;

	std::cout << std::fixed << std::setprecision(10) << ans << "\n";

二:考虑计算每一个位置硬币的期望。根据期望的线性性算出 \(n\) 个硬币的期望。
考虑每一块硬币,当前这枚硬币面值为 \(a_i\) 。

它能被翻面当且仅当它左边存在硬币的面值为 \(a_i\) 的约数。它能正面朝上当且仅当它左边有且仅有偶数枚硬币面值为 \(a_i\) 的约数。

可以用 \(O(n^{2}\) 或 \(O(M \log M)\) 的算法统计出每种面值的约数面值个数(\(M\) 是值域大小)。假设不包括自己这枚,存在 \(w_i\) 枚硬币的面值是 \(a_i\) 的约数。

这枚硬币正面朝上的期望是:这枚硬币在 \(w_i + 1\) 枚硬币(包括自己)中排在奇数位置的期望。

\[E(X_i) = \frac{\lceil \frac{w_i + 1}{2} \rceil}{w_i + 1} = \frac{\lfloor \frac{w_i + 1 + (2 - 1)}{2} \rfloor}{w_i + 1} = \frac{\lfloor \frac{w_i}{2} \rfloor + 1}{w_i + 1} \]

由期望的线性性,所有 \(n\) 枚硬币正面朝上的期望是

\[E(Y) = E(\sum_{i = 1}^{n} X_i) = \sum_{i = 1}^{n} \frac{\lfloor \frac{w_i}{2} \rfloor + 1}{w_i + 1} \]

这种算法需要先预处理出每个硬币的不包括自己的倍数面值硬币的数量。时间复杂度是 \(O(n^{2})\) 或 \(O(M \log M)\) 。
每个硬币的期望计算复杂度是 \(O(1)\) ,期望求和复杂度是 \(O(n)\) 。于是总时间复杂度为 \(T(n + min(n^{2}, M \log M)) = O(min(n^{2}, M \leq M))\) 。可以通过 \(n \leq 20000 \vee M \leq 10^{6}\) 的数据。

solution2
	int n; std::cin >> n;
	std::vector<int> a(n + 1);
	for (int i = 1; i <= n; i++) std::cin >> a[i];
	double ans = 0;
	for (int i = 1; i <= n; i++) {
		int cnt = 0;
		for (int j = 1; j <= n; j++) if (j != i && a[i] % a[j] == 0) {
			cnt += 1;
		}
		cnt += 1;
		ans += 1. * ((cnt + 1) / 2) / cnt;
	}
	std::cout << std::fixed << std::setprecision(10) << ans << "\n";

D

题意

在一个 \(W \times H\) 的矩阵上,每个位置放置一块金块。有 \(N\) 个金块收集器,坐标为 \((x_i, y_i), (i = 1, 2, 3, \cdots, N), 1 \leq x_i \leq W, 1 \leq y_i \leq H\) 。按笛卡尔坐标系给出。

一块区域的金块被收集后,这块区域不再有金块。

每个收集器可以以自己为中心,向上、下、左、右延申收集金块。如果收集到金块,可以选择继续延申,否则这个方向不能继续延申。

询问使用这 \(N\) 个收集器最多能获得多少金块。

题解

首先注意一下坐标系是笛卡尔坐标系。

注意到: 对于某个正确答案,一定是 \(n\) 个点的某个排列,按顺序向四个方向延申到最长得到的答案。

可以想到暴力做法,\(O(n!)\) 枚举一个排列,然后 \(O(n)\) 顺序枚举每个点,再 \(O(n)\) 对网格染色。最终可以 \(O(n^{2} n!)\) 的时间复杂度计算最大染色块数。

继续观察到: 选择任意一个点按四个方向进行延申到最长,等价于把当前的矩形区域且分成四个小矩形。

于是任意一个矩形区域,都可以由若干种方式的四个小矩形合并。取最优的合并方案计算当前矩形的最优答案,就是动态规划。

定义 f[左下角横坐标][左下角纵坐标][右上角横坐标][右上角纵坐标] 表示的这个矩形的最大染色数。

可以使用类似区间 DP 的形式,枚举其中一个端点,再枚举边长。最终可以 \(O(H^{2}W^{2})\) 枚举出所有矩形。

对于每个矩形,\(O(N)\) 检查在内部的点并转移。不存在特殊点的矩形答案是 \(0\) 。于是算法的总时间复杂度可以达到 \(O(N H^{2}W^{2})\) 。

注意到: 有效矩形的数量不多。

一个有效矩形的有效子矩形最多只有 \(N \times 4 = O(N)\) 个。即总的有效矩形只有 \(O(N^{2})\) 个。

可以使用非递推的方式,即使用记忆化的递归,以 \(O(N^{2})\) 的时间复杂度访问到所有状态,并 \(O(N)\) 转移。算法的总时间复杂度可以达到 \(O(N^{3})\) 。

\(f\)[左下角横坐标 \(x_1\)][左下角纵坐标 \(y_1\)][右上角横坐标 \(x_2\)][右上角纵坐标 \(y_2\)] 的转移中,枚举到任意一个内部的特殊点 \((cx, cy)\) 。则由四个矩形:

  • rec[\(x_1\)][\(y_1\)][\(cx - 1\)][\(cy - 1\)]
  • rec[\(cx + 1\)][\(cy + 1\)][\(x_2\)][\(y_2\)]
  • rec[\(x_1\)][\(cy + 1\)][\(cx - 1\)][\(y_2\)]
  • rec[\(cx + 1\)][\(y_1\)][\(x_2\)][\(cy - 1\)]

转移。

另外注意有效子矩形的状态应该是:非空、不越界。

view
	int W, H; std::cin >> W >> H;
	int N; std::cin >> N;
	std::vector<std::array<int, 2> > pos(N + 1);
	for (int i = 1; i <= N; i++) std::cin >> pos[i][0] >> pos[i][1];
	std::map<std::array<int, 4> , int> f;
	std::function<int(int, int, int, int)> dfs = [&] (int x1, int y1, int x2, int y2) -> int {
		if (f.count({x1, y1, x2, y2})) return f[{x1, y1, x2, y2}];
		int &res = f[{x1, y1, x2, y2}];
		res = 0;
		auto calc = [&] (int nx1, int ny1, int nx2, int ny2) -> int {
			if (!(1 <= nx1 && nx1 <= nx2 && nx2 <= W && 1 <= ny1 && ny1 <= ny2 && ny2 <= H)) return 0;
			if (nx1 < x1 || ny1 < y1 || x2 < nx2 || y2 < ny2) return 0;
			return dfs(nx1, ny1, nx2, ny2);
		};
		for (int i = 1; i <= N; i++) {
			int cx = pos[i][0], cy = pos[i][1];
			if (cx < x1 || cx > x2 || cy < y1 || cy > y2) continue;
			res = std::max(res, (x2 - x1 + 1) + (y2 - y1 + 1) - 1 +
			calc(x1, y1, cx - 1, cy - 1) + calc(x1, cy + 1, cx - 1, y2) + 
			calc(cx + 1, y1, x2, cy - 1) + calc(cx + 1, cy + 1, x2, y2) );
		}
		return res;
	};
	dfs(1, 1, W, H);
	std::cout << f[{1, 1, W, H}] << "\n";
posted @ 2024-08-10 22:55  03Goose  阅读(52)  评论(0)    收藏  举报