返回顶部

AtCoder Beginner Contest 236 题解

AtCoder Beginner Contest 236

目前补到F

A - chukodai

题目描述:给你一个字符串\(s\),下标从1开始,再给你两个整数a , b,将字符串中位置a和位置b的字符交换位置并输出交换后的字符串。

思路:根据题意模拟即可

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

参考代码:

void solve() {
	string s;
	cin >> s;
	int a, b;
	cin >> a >> b;
	swap(s[a - 1], s[b - 1]);
	cout << s << '\n';
}

B - Who is missing?

题目描述:给你\(4 * n - 1\)张卡片,每张卡片上有一个\(1 \sim n\)之间的数字,同一数字最多出现4次,有一个数字只出现了三次,请将这个数字找出来。

思路:根据题意模拟即可

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

参考代码:

void solve() {
	int n;
	cin >> n;
	vector<int>cnt(n + 1, 0);
	int a;
	for (int i = 1; i < 4 * n; ++i) cin >> a, cnt[a]++;
	for (int i = 1; i <= n; ++i) {
		if (cnt[i] == 3) {
			cout << i << '\n';
			return;
		}
	}
	return;
}

C - Route Map

题目描述:给你两个集合\(S , T\),问\(S\)的每个元素是否在\(T\)中出现过,对于每个元素,若出现过输出Yes,否则输出No

思路:使用setmap等数据结构存储\(T\)中的元素,然后遍历\(S\)中的元素在使用的数据结构中查找,根据查找结果输出相应的答案即可。

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

参考代码:

void solve() {
	int n, m;
	cin >> n >> m;
	vector<string>strs(n);
	for (int i = 0; i < n; ++i) cin >> strs[i];
	map<string, bool>mp;
	string s;
	for (int i = 1; i <= m; ++i) {
		cin >> s;
		mp[s] = true;
	}
	for (auto s : strs) {
		if (mp.count(s)) cout << "Yes" << '\n';
		else cout << "No" << '\n';
	}
	return;
}

D - Dance

题目描述:有\(2 * n\)个人,让你将其分成\(n\)组,每组中的两个人会有一个匹配值\(B_{i , j}\)其中i , j表示人的编号。问对于所有的分组方案,每一组的匹配值的异或和的最大值是多少。假设每组的匹配值为\(A_i\),即求\(A_1 \oplus A_2 \oplus ... \oplus A_n\)的最大值。

数据范围:\(1 \leq n \leq 8 , 0 \leq B_{i , j} < 2^{30}\)

思路:显然可以爆搜,因为要配对,如果当前已经枚举的人数为偶数,那么任选一个人作为下一组即可,若当前已经分配好的是奇数个,那么就要遍历剩下的人与前一个人组成一队。

时间复杂度:\(O((2 * n - 1)!!)\)

参考代码:

void solve() {
	int n;
	cin >> n;
	vector<vector<int>>a(2* n + 1, vector<int>(2 * n + 1, 0));
	for (int i = 1; i <= 2 * n - 1; ++i) {
		for (int j = i + 1; j <= 2 * n; ++j) cin >> a[i][j];
	}
	int res = 0, m = n << 1;
	vector<bool>vis(2 * n + 1, false);
	auto dfs = [&](auto dfs, int cur, int cnt, int ans) {
		if (cnt == m) {
			res = max(res, ans);
			return;
		}
		if (cnt % 2 == 0) {
			for (int i = 1; i <= m; ++i) {
				if (vis[i] == true) continue;
				vis[i] = true;
				dfs(dfs, i, cnt + 1, ans);
				vis[i] = false;
				break;
			}
		}
		else {
			for (int i = cur + 1; i <= m; ++i) {
				if (vis[i] == true) continue;
				vis[i] = true;
				int u = cur, v = i;
				dfs(dfs, i, cnt + 1, ans ^ a[u][v]);
				vis[i] = false;
			}
		}
	};
	vis[1] = true;
	dfs(dfs, 1, 1, 0);
	cout << res << '\n';
	return;
}

E - Average and Median

题目描述:给你一个长度为\(n\)的序列\(a\),你需要从中选出一些元素组成序列\(b\),选取规则为:\(\forall 1 \leq i < n - 1\)\(a_{i}\)\(a_{i - 1}\)至少有一个在序列\(b\)中。求序列\(b\)的均值的最大值和最大中位数。

数据范围:\(2 \leq n \leq 10^5 , 1 \leq a_i \leq 10^9\)

思路:先考虑这样一个问题,若按照题目的选取规则,求选取的和最大。那么显然可以dp,定义状态\(f_i , g_i\)分别表示不选第\(i\)个数和选第\(i\)个数字,那么转移方程显然为:

\[\begin{cases} f_i = g_{i - 1}\\ g_i = max(f_{i -1} , g_{i - 1}) + a_i \end{cases} \]

因为只与前一项值有关,所以该递推式可以\(O(n)\)时间\(O(1)\)空间求解。

回到原问题:我们可以二分平均值的最大值mid,那么将每一项a_i-mid作为c_i,那么原问题就等价于求按照上述选取规则使得\(b\)序列的和大于等于0。该过程可以使用上述方法\(O(n)\)求解。故求均值的最大值的复杂度为:\(O(nlog(\mathop{max}\limits_{1\leq i \leq n}\;a_i))\)

再来看第二个问题,这是一个比较经典的问题,我们同样可以二分最大中位数\(mid\),然后将序列\(a\)数字小于\(mid\)的置为-1,将数字大于等于\(mid\)的置为1,那么原问题又转化为按照上述规则选取序列\(a\)中的元素,使得最大和大于0。该过程使用上述方法同样可以\(O(n)\)求解,总复杂度为\(O(nlogn)\)

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

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

参考代码:

template<class T>
T maximize(vector<T>& nums) {
	T f = 0, g = 0;
	for (auto& num : nums) {
		T u = max(f, g) + num;
		f = g;
		g = u;
	}
	return max(f, g);
}
void solve() {
	int n;
	cin >> n;
	vector<int>nums(n, 0);
	for (auto& num : nums) cin >> num;
	auto average = [&]()->void {
		double lr = 1.0, rs = 1e9 + 5;
		vector<double>b(n);
		while ((rs - lr) / rs > 3e-7) {
			double mid = sqrt(lr * rs);
			for (int i = 0; i < n; ++i) b[i] = nums[i] - mid;
			if (maximize(b) >= 0.0) lr = mid;
			else rs = mid;
		}
		cout << fixed << setprecision(10) << lr << '\n';
		return;
	};
	average();
	auto median = [&]()->void {
		int lr = 1, rs = 1e9 + 5;
		vector<int>b(n);
		while (lr + 1 < rs) {
			int mid = lr + rs >> 1;
			for (int i = 0; i < n; ++i) b[i] = nums[i] >= mid ? 1 : -1;
			if (maximize(b) > 0) lr = mid;
			else rs = mid;
		}
		cout << lr << '\n';
		return;
	};
	median();
	return;
}

F - Spices

题目描述:有\(2^n - 1\)个数字\(1 , 2 , ... , 2^n - 1\),获得一个数字需要花费\(c_i\),你的目的是在这些数字中选择一些数字组成集合\(S\),在集合\(S\)中选择一些数字进行异或和可以得到\(1 \sim 2^n - 1\)之间的所有数字,求最小的花费。

思路:从求集合\(S\)的角度来看,是一个比较裸的线性基,学习线性基的相关知识可以参考此处。要使花费最小,可以将数字和花费绑定并按照花费从小到大排序,然后正常求解即可。

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

参考代码:

void solve() {
	int n, val;
	cin >> n;
	int m = 1 << n;
	vector<pair<int, int>>a;
	for (int i = 1; i < m; ++i) {
		cin >> val;
		a.push_back({ val , i });
	}
	std::sort(a.begin(), a.end());
	vector<int>p(n + 1, 0);
	long long res = 0;
	for (int i = 1; i <  m; ++i) {
		int tmp = a[i - 1].second;
		for (int j = n; j >= 0; --j) {
			if (((tmp >> j) & 1) == 0) continue;
			if (p[j] == 0) {
				p[j] = tmp;
				res += a[i - 1].first;
				break;
			}
			else tmp ^= p[j];
		}
	}
	cout << res << '\n';
	return;
}
posted @ 2022-01-25 16:06  cherish-lgb  阅读(383)  评论(1编辑  收藏  举报