AtCoder Beginner Contest 243 题解


A - Shampoo

题目描述:给你\(V\)升水,有三个人,依次用\(A , B , C\)升,循环使用,水不会补充,求谁使用的时候水不够。

思路:先用\(V\)\(A + B + C\)然后再讨论即可

时间复杂度:\(O(1)\)

参考代码:

void solve() {
	int V, A, B, C;
	cin >> V >> A >> B >> C;
	int sum = 0;
	sum = A + B + C;
	V %= sum;
	if (V < A) cout << "F" << '\n';
	else if (V < A + B) cout << "M" << '\n';
	else cout << "T" << '\n';
	return;
}

B - Hit and Blow

题目描述:给你两个长度为\(n\)的数组\(a , b\),求分别满足条件:\(a_i = b_j , i = j , \forall 1 \leq i \leq n , 1 \leq j \leq n\)\((i , j)\)\(a_i = b_j , i \neq j , \forall 1 \leq i \leq n , 1 \leq j \leq n\)\((i , j)\)的数量。

思路:考虑到\(1 \leq n \leq 1000\),所以暴力枚举每一对\((i , j)\)进行判断即可

时间复杂度:\(O(n^2)\)

参考代码:

void solve() {
	int n;
	cin >> n;
	vectora(n + 1, 0), b(n + 1, 0);
	for (int i = 1; i <= n; ++i) cin >> a[i];
	for (int i = 1; i <= n; ++i) cin >> b[i];
	int cnt = 0, ct = 0;
	for (int i = 1; i <= n; ++i) {
		for (int j = 1; j <= n; ++j) {
			cnt += (a[i] == b[j] && i == j);
			ct += (a[i] == b[j] && i != j);
		}
	}
	cout << cnt << "\n" << ct << '\n';
	return;
}

C - Collision 2

题目描述:二维平面上给定一些运动的点,并给定其初始时的运动方向,运动方向只有沿着\(x\)轴的负方向或者正方向移动,问是否存在碰撞。

思路:首先不同\(y\)坐标的点一定不会发生碰撞,故将\(y\)相同的点放一起讨论,不发生碰撞只有可能是以下两种情况:

  • 所有点的初始时的运动方向一致
  • 前半部分的运动方向是\(x\)的负方向,后半部分是\(x\)的正方向

故根据这两种情况讨论即可,考虑到\(y\)很大,使用unordered_map映射一下即可。

时间复杂度:\(O(nlogn)\)

参考代码:

void solve() {
	int n;
	cin >> n;
	using PII = pair;
	unordered_map>mp;
	sets;
	int x, y;
	for (int i = 1; i <= n; ++i) {
		cin >> x >> y;
		mp[y].push_back({ x , i - 1 });
		s.insert(y);
	}
	for (auto& iter : s) sort(mp[iter].begin(), mp[iter].end());
	string str;
	cin >> str;
	for (auto& iter : s) {
		auto& vec = mp[iter];
		char c = str[vec[0].second];
		int cnt = 1;
		for (auto& v : vec) {
			if (c != str[v.second]){
				if (cnt == 0 || c == 'R') {
					cout << "Yes\n";
					return;
				}
				else {
					cnt = 0;
					c = str[v.second];
				}
			}
		}
	}
	cout << "No" << '\n';
	return;
}

D - Moves on Binary Tree

题目描述:给你一个无限大的二叉树,二叉树的结点编号满足条件:若父结点的编号为\(i\),则左儿子的编号为\(2i\),右儿子的编号为\(2i + 1\),给定一个移动命令和初始位置编号\(x\),循环执行\(n\)次,问执行完毕后所在位置的编号,最终答案不超过\(10^{18}\)

思路:比较简单的模拟题,考虑到过程中可能会超过long long的最大表示范围,所以将\(x\)转化成二进制数组,然后进行模拟。

时间复杂度:\(O(n)\)

参考代码:

void solve() {
	long long n, x;
	string s;
	cin >> n >> x >> s;
	int m = s.size(), idx = 0;
	vectora;
	while (x) {
		a.push_back(x % 2);
		x /= 2;
	}
	reverse(a.begin(), a.end());
	int id = a.size() - 1;
	for (int i = 1; i <= n; ++i) a.push_back(0);
	for (int i = 1; i <= n; ++i) {
		if (s[idx] == 'U') --id;
		else if (s[idx] == 'L') a[++id] = 0;
		else a[++id] = 1;
		idx = (idx + 1) % m;
	}
	for (int i = 0; i <= id; ++i) x = (x << 1) + a[i];
	cout << x << '\n';
	return;
}

E - Edge Deletion

题目描述:给你\(n\)个点\(m\)条边的无向连通图,问最多删除多少条边,使得剩下的图满足以下条件:

  • 图仍然连通
  • 删除边后的图中任意两点之间的距离等于没删边的图中任意两点之间的距离

思路:首先可以考虑删除某条边,然后进行检验,时间复杂度:\(O(n^5)\)。我们可以考虑先使用Floyd算法求出任意两点之间的最短距离定义\(f_{i , j}\)表示顶点\(i , j\)之间的最短距离,然后我们枚举每一条边\((u , v , c)\),若\(\exist 1 \leq i \leq n , i \neq u , i \neq v\)使得\(f_{u , i} + f_{i , v} \leq c\),那说明该条边是可以删去的,考虑到要排除\(i\)\(u ,v\)相等的情况,可以将\(f_{i , i}\)的初始值设置为\(\infin\)用于简化代码:

时间复杂度:\(O(n^3)\)

参考代码:

void solve() {
	int n, m;
	cin >> n >> m;
	vector>edges;
	vector>f(n + 1, vector(n + 1, 0x3f3f3f3f3f3f3fll));
	int u, v, w;
	for (int i = 1; i <= m; ++i) {
		cin >> u >> v >> w;
		edges.push_back({ u ,v , w });
		f[u][v] = f[v][u] = w;
	}
	for (int k = 1; k <= n; ++k) {
		for (int i = 1; i <= n; ++i) {
			for (int j = 1; j <= n; ++j) {
				f[i][j] = min(f[i][j], f[i][k] + f[k][j]);
			}
		}
	}
	int res = 0;
	for (auto&& [u, v, w] : edges) {
		int cnt = 0;
		for (int i = 1; i <= n; ++i) {
			if (f[u][i] + f[i][v] <= w) cnt = 1;
		}
		res += cnt;
	}
	cout << res << '\n';
	return;
}
ABC