返回顶部

Codeforces Round #764 (Div. 3)题解

Update:2022/1/11 19:57 全部补完了

回家的时候太晚了,没打成 QAQ

A. Plus One on the Subset

题目描述:给你一个长度为\(n\)的数组\(A\),求将数组\(A\)中的所有元素变相等的最小操作次数,一次操作可以选择任意多个下标的元素,然后对其加一。

思路:显然答案为最大值和最小值的差值。

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

参考代码:

void solve() {
	cin >> n;
	int a, mx(0), mn(INT_MAX);
	for (int i = 1; i <= n; ++i) {
		cin >> a;
		mx = max(mx, a);
		mn = min(mn, a);
	}
	cout << mx - mn << '\n';
	return;
}

B. Make AP

题目描述:给你一个长度为\(3\)的数组\(A\),你需要判断是否存在一个正整数\(m\),使得其乘以数组中的某一元素后的新数组是一个等差数列,比如\([1 , 4 , 6]\)\(m = 2\),然后新数组可以是\([2 , 4 , 6]\),此时这个数组是等差数列,满足题解。若存在就输出YES,否则输出NO

思路:因为数组长度为\(3\),所以我们可以枚举那个变化的数字,然后根据另外两个不变的数字确定公差然后进行验证即可。

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

参考代码:

long long a, b, c;
bool check(long long target, long long val) {
	if (target % val != 0 || target / val <= 0) return false;
	cout << "YES" << '\n';
	return true;
}
void solve() {
	cin >> a >> b >> c;
	//讨论那两个数不变即可
	//b c 不变
	long long dx = c - b;
	if (check(b - dx, a)) return;
	//a c 不变
	dx = c - a;
	if (dx % 2 == 0 && check(c - dx / 2, b)) return;
	// a b 不变
	dx = b - a;
	if (check(b + dx, c)) return;
	cout << "NO" << '\n';
	return;
}

C. Division by Two and Permutation

题目描述:给你一个长度为\(n\)的数组\(A\),一次操作可以选择数组中的元素,然后将其修改为\(\lfloor \frac{a_i}{2}\rfloor\)。问你是否可以通过操作将数组变成一个\(1\sim n\)的排列,是输出YES,否则输出NO

思路:显然若存在,则最终答案的数字是确定的,我们可以直接枚举每一个数组中的元素,然后若当前元素在\(1\sim n\)且前面没有出现过,就将该位置标记,否则一直除以\(2\),直到该元素为\(0\)或者找到一个没有标记的位置。若最终该元素为\(0\)输出NO,否则输出YES

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

参考代码:

int n;
void solve() {
	cin >> n;
	vector<int>a(n + 1, 0), cnt(n + 1, 0);
	for (int i = 1; i <= n; ++i) cin >> a[i];
	for (int i = 1; i <= n; ++i) {
		while (a[i] && (a[i] > n || cnt[a[i]])) a[i] /= 2;
		if (a[i] == 0) {
			cout << "NO" << '\n';
			return;
		}
		cnt[a[i]] = 1;
	}
	cout << "YES" << '\n';
	return;
}

D. Palindromes Coloring

题目描述:给你一个长度为\(n\)的字符串\(s\)和一个整数\(k\),你需要将字符串\(s\)中的字符分成\(k\)组,但\(s\)中的字符不必全部使用完。然后每一组的字符通过重排之后都是一个回文串,求划分后回文串长度的最小的最大值。

思路:比较显然的二分答案,二分最终的最大值,然后去验证即可。注意过程中需要分奇回文和偶回文进行讨论。

时间复杂度:\(O(n + 26logn)\)

参考代码:

int n, k;
string s;
void solve() {
	cin >> n >> k >> s;
	int lr = 2, rs = n, res = 1;
	vector<int>cnt(27, 0);
	for (auto& c : s) ++cnt[c - 'a'];
	while (lr <= rs) {
		int mid = lr + rs >> 1;
		int ct = 0;//能组成的长度为mid的回文数目
		if (mid & 1) {//奇回文
			int cur = 0, cnt1 = 0, cnt2 = 0;
			//先将长度为mid - 1的偶回文求出来,然后进行调整
			for (auto& c : cnt) {
				cur += (c & 1) ? c - 1 : c;
				cnt1 += cur / (mid - 1);
				cur %= (mid - 1);
				cnt2 += c & 1;//剩余的字符
			}
			cnt2 += cur;
			while (cnt1 > 0) {
				int dx = min(cnt2, cnt1);
				ct += dx;
				cnt2 -= dx;
				cnt1 -= dx;
				--cnt1;
				cnt2 += mid - 1;
			}
		}
		else {//偶回文
			int cur = 0;
			for (auto& c : cnt) {
				cur += (c & 1) ? c - 1 : c;
				ct += cur / mid;
				cur %= mid;
			}
		}
		if (ct >= k) lr = mid + 1, res = mid;
		else rs = mid - 1;
	}
	cout << res << '\n';
	return;
}

E. Masha-forgetful

题目描述:给你\(n\)个长度为\(m\)的串,再给你一个长度为\(m\)的串\(s\),若将\(s\)分割成长度至少为\(2\)的子串,这些子串若都出现在这\(n\)个串的子串中,则输出相应的方案,否则输出-1

思路:因为题目没有限制最小化分割数,所以我们可以将其分割成长度为\(2\)和长度为\(3\)的串,然后判断,考虑到\(n \times m \leq 10^6\)。我们可以暴力的将这\(n\)个串的长度为\(2\)和长度为\(3\)的子串处理出来然后用map统计,考虑到这些字符串只含有数字,所以将这些子串表示成相对应的十进制数,若存在冲突,使用一些简单的方式解决一下即可,当然也可以以使用桶替换map,然后就是愉快的\(dp\)求可行性即可,最后使用dfs求出分割方案即可。

时间复杂度:\(n \times m log (n\times m)\)

参考代码:

int n, m;
string s;
struct infor {
	int lr, rs, idx;
	infor(int _lr = 0 , int _rs = 0 , int _idx = 0):lr(_lr) , rs(_rs) , idx(_idx){}
};
void solve() {
	cin >> n >> m;
	map<int, infor>mp;//字符串 出现在第几个字符串 以及区间
	auto fun = [&](int x) {//对于三位数且有前导0的进行处理
		if (x >= 100) return x;
		return x + 1000;
	};
	//两位数有前导0不用管,三位数有前导0转化成四位数
	for (int i = 1; i <= n; ++i) {
		cin >> s;
		for (int j = 0; j < m - 1; ++j) {
			int dx = 0;
			for (int k = j; k <= j + 1; ++k) dx = dx * 10 + s[k] - '0';
			if (mp.count(dx)) continue;
			else mp[dx] = { j + 1 , j + 2 , i };
		}
		for (int j = 0; j < m - 2; ++j) {
			int dx = 0;
			for (int k = j; k <= j + 2; ++k) dx = dx * 10 + s[k] - '0';
			dx = fun(dx);
			if (mp.count(dx)) continue;
			else mp[dx] = { j + 1 , j + 3 , i };
		}
	}
	cin >> s;
	s = ' ' + s;
	if (m == 1) {
		cout << -1 << '\n';
		return;
	}
	vector<vector< int >> f(2, vector<int>(m + 1, -1));
	f[0][0] = 0;
	f[1][0] = 0;
	for (int i = 2; i <= m; ++i) {
		int dx = 0, dy = 0;
		for (int j = i - 1; j <= i; ++j) dx = dx * 10 + s[j] - '0';
		if ((f[0][i - 2] >= 0 || f[1][i - 2] >= 0) && mp.count(dx)) f[0][i] = dx;
		if (i >= 3) {
			for (int j = i - 2; j <= i; ++j) dy = dy * 10 + s[j] - '0';
			dy = fun(dy);
			if ((f[1][i - 3] >= 0 || f[0][i - 3] >= 0) && mp.count(dy)) f[1][i] = dy;
		}
	}
	if (f[0][m] == -1 && f[1][m] == -1) {
		cout << -1 << '\n';
		return;
	}
	vector<infor> res;
	bool flag = false;
	auto dfs = [&](auto dfs, int cur) {
		if (cur == 0) {
			flag = true;
			return;
		}
		for (int i = 0; i <= 1; ++i) {
			if (f[i][cur] == -1) continue;
			res.push_back(mp[f[i][cur]]);
			dfs(dfs, cur - (i == 0 ? 2 : 3));
			if (flag) return;
			res.pop_back();
		}
		return;
	};
	dfs(dfs, m);
	cout << res.size() << '\n';
	reverse(res.begin(), res.end());
	for (auto& [lr, rs, idx] : res) cout << lr << " " << rs << " " << idx << '\n';
	return;
}

F. Interacdive Problem

题目描述:交互题,给你一个整数\(n\),你需要猜出一个整数\(x , 1\leq x < n\)。交互方式为你给定\(c\),然后\(x = x + c\),并告知你\(\lfloor \frac{x}{n}\rfloor\)。要求次数不超过\(10\)\(1 \leq n \leq 1000\)

思路:设\(y = x , t = \sum c\),则(注:下述的除法都是向下取整除法)

\[\begin{align} 0 &< y < n\\ t &< y + t < n + t\\ \frac{t}{n} &\leq \frac{y + t}{n} \leq \frac{t}{n} + 1\\ \end{align} \]

\(\frac{x}{n} = \frac{t}{n} + 1\),其中\(x = y + t\),则

\[\frac{y + t}{n} = \frac{t}{n} + 1\\ y + t \geq n * \frac{t}{n} + n\\ y + t \geq t - t \% n + n\\ y \geq n - t \% n\\ \]

\(\frac{x}{n} = \frac{t}{n}\),则

\[\frac{x}{n} = \frac{t}{n}\\ \frac{y + t}{n} = \frac{t}{n}\\ y + t \geq \frac{t}{n} * n\\ y + t \geq t - t \% n\\ y \geq n- t \% n \]

所以我们考虑二分答案求\(y\),每次二分出来的是\(mid\),但实际的\(y\)\(mid + 1\),那么我们的目标就是让自己枚举出来的\(y\)加上\(t\)恰好是\(n\)的倍数。每次询问的是\(y + \sum c\)还再需加多少才能变成\(n\)的倍数。

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

参考代码:

void solve() {
	int n(0);
	cin >> n;
	int lr = 1, rs = n - 1;
	int last = 0, sum = 0;
	auto query = [&](int val) {
		if (val == 0) return last;
		cout << "+ " << val << endl;
		sum += val;
		int cur(0);
		cin >> cur;
		last = cur;
		return cur;
	};
	while (lr < rs) {
		int mid = lr + rs >> 1;
		int add = ((n - mid - sum - 1) % n + n) % n;
		int target = (mid + 1 + sum + add) / n;
		int res = query(add);
		if (res == target) lr = mid + 1;
		else rs = mid; 
	}
	cout << "! " << lr + sum << endl;
	return;
}

G. MinOr Tree

题目描述:给你一个\(n\)个点\(m\)条边的带权无向图,求其所有的生成树中,所有边权的或值的最小值。

思路:考虑二分答案,二分最终的异或值设其为mid,然后遍历所有边,若\(w | mid \leq mid\)则将这条边加入并查集中,最后检验选出的这些边是否能构成一个连通图,若能就更新答案并\(rs= mid - 1\),若不能就\(lr = mid + 1\)

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

参考代码:

int n, m;
struct Edge {
	int u, v, w;
	Edge(int _u = 0, int _v = 0, int _w = 0) :u(_u), v(_v), w(_w) {}
};
void solve() {
	cin >> n >> m;
	int u, v, w;
	vector<Edge> edges;
	for (int i = 1; i <= m; ++i) {
		cin >> u >> v >> w;
		edges.push_back({ u , v , w });
	}
	vector<int>father(n + 1, -1);
	auto find = [&](auto f, int x)-> int {
		if (father[x] == -1) return x;
		return father[x] = f(f, father[x]);
	};
	auto Union = [&](int u, int v)->void {
		u = find(find, u);
		v = find(find, v);
		if (u == v) return;
		father[u] = v;
		return;
	};
	int lr = 0, rs = INT_MAX, res = 0;
	while (lr <= rs) {
		int mid = lr + rs >> 1;
		father = vector<int>(n + 1, -1);
		for (auto& [u, v, w] : edges)
			if ((w | mid) <= mid) Union(u, v);
		int cnt = 0;
		for (int i = 1; i <= n; ++i) cnt += father[i] == -1;
		if (cnt == 1) res = mid, rs = mid - 1;
		else lr = mid + 1;
	}
	cout << res << '\n';
	return;
}
posted @ 2022-01-11 11:36  cherish-lgb  阅读(286)  评论(0编辑  收藏  举报