Squarepoint Challenge (Codeforces Round 1055, Div. 1 + Div. 2)


A. Increase or Smash

题意:你有一个全\(0\)数组,每次可以使得数组全部加上一个数或者使得某些位置变为\(0\)。求变成\(a\)的最小操作数。

最少的操作方案是,先把\(a\)去重后排序,然后从大到小每次加\(a_i - a_{i-1}\),然后把小于\(a_i\)的位置都变成\(0\)。这样等于\(a_i\)的位置就总共加上了\(a_i - a_{i-1} + a_{i-1} - a_{i-2} ...\)。最后就等于\(a_i\)。然后最后一步是直接加最小值就变成了\(a\)数组,不需要再把一些位置变成\(0\)。所有操作数就是\(2\)乘种类数减一。

点击查看代码
#include <bits/stdc++.h>

using i64 = long long;

void solve() {
	int n;
	std::cin >> n;
	std::vector<int> a(n);
	for (int i = 0; i < n; ++ i) {
		std::cin >> a[i];
	}

	std::set<int> s(a.begin(), a.end());
	std::cout << (int)s.size() * 2 - 1 << "\n";
}

int main() {
	std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
	int t = 1;
	std::cin >> t;
	while (t -- ) {
		solve();
	}
	return 0;
}

B. Catching the Krug

题意:一个\((n+1) \times (n + 1)\)的网格,有两个人分别在\((x_1, y_1), (x_2, y_2)\)。第二个人要抓第一个人。第一个人只能上下左右移动,第二个人还可以斜着移动。两个人都不能出界。求回合数。

如果不考虑第一个人移动,则有\(\max(|x_1 - x_2|, |y_1 - y_2|)\)回合。
现在考虑第一个人移动,那么它横纵坐标移动的方向是固定的,肯定是朝远离第二个人的方向,于是可以计算出其横纵坐标分别多少步到边界。那么第一个人肯定一直走一个坐标是最优的,于是两个坐标可以走的距离取一下最大值。注意特判一下两个人有一个坐标相同的情况。

点击查看代码
#include <bits/stdc++.h>

using i64 = long long;

void solve() {
	int n;
	i64 x1, y1, x2, y2;
	std::cin >> n >> x1 >> y1 >> x2 >> y2;
	i64 dx = x1 <= x2 ? x1 : n - x1;
	i64 dy = y1 <= y2 ? y1 : n - y1;
	i64 X = std::abs(x1 - x2), Y = std::abs(y1 - y2);
	i64 ans = 0;
	if (x1 == x2) {
		ans = Y + dy;
	} else if (y1 == y2) {
		ans = X + dx;
	} else {
		ans = std::max(X + dx, Y + dy);
	}
	std::cout << ans << "\n";
}

int main() {
	std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
	int t = 1;
	std::cin >> t;
	while (t -- ) {
		solve();
	}
	return 0;
}

C. Triple Removal

题意:一个\(01\)数组的代价为,每次选择三个相同元素,位置为\(i, j, k\)。花费\(\min(j - i, k - j)\)的代价。然后这三个位置被删除,一直到把整个数组删掉。给你一个\(01\)数组,每次询问其一个子数组的代价。

首先\([l, r]\)这个子数组\(0\)的个数和\(1\)的个数都应该是\(3\)的倍数。
然后考虑一个数组的代价,发现除了\(010101\)这种\(01\)交替的数组,每次操作选择的三个位置里都可以有两个是相邻的,也就是代价为\(1\)。而\(01\)交替的情况,第一次操作代价为\(2\),其余操作也为\(1\)了。那么只需要判断这个子数组是不是\(01\)交替的就行,可以前缀和记录\(a_i = a_{i+1}\)的个数。

点击查看代码
#include <bits/stdc++.h>

using i64 = long long;

void solve() {
	int n, q;
	std::cin >> n >> q;
	std::vector<int> a(n);
	for (int i = 0; i < n; ++ i) {
		std::cin >> a[i];
	}

	std::vector<int> sum0(n + 1), sum1(n + 1), sum(n + 1);
	for (int i = 0; i < n; ++ i) {
		sum0[i + 1] = sum0[i] + (a[i] == 0);
		sum1[i + 1] = sum1[i] + (a[i] == 1);
		sum[i + 1] = sum[i];
		if (i + 1 < n && a[i] == a[i + 1]) {
			sum[i + 1] += 1;
		}
	}

	while (q -- ) {
		int l, r;
		std::cin >> l >> r;
		if ((sum0[r] - sum0[l - 1]) % 3 || (sum1[r] - sum1[l - 1]) % 3) {
			std::cout << -1 << "\n";
			continue;
		}

		int ans = (r - l + 1) / 3;
		if (sum[r - 1] - sum[l - 1] == 0) {
			ans += 1;
		}
		std::cout << ans << "\n";
	}
}

int main() {
	std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
	int t = 1;
	std::cin >> t;
	while (t -- ) {
		solve();
	}
	return 0;
}

D. Division Versus Addition

题意:一个数组,两个人玩游戏。第一个人每次把一个数除二(向下取整)。第二个人每次把一个数加一。两个人每次只能操作大于\(1\)的数。当所有数都变成\(1\)游戏结束。第一个人想尽快结束,第二个人想慢点结束。每次求一个子数组的回合数。

考虑有哪些数可以使得回合数多一,有两种情况,一种是需要从一开始就一直加一,例如\(3, 5\)这种,还有被除多个\(2\)后会变成第一种数,这样第二个人变成先手,可以使得这个数回合数加一,例如\(7, 10\)这种。

那么记录一下这两种数的个数的前缀和。记子数组里原来数字的操作总数为\(sum\),第一种数个数为\(sum1\),第二种数个数为\(sum2\),那么第一个人肯定先把第一种数除二,然后第二个人把剩下的第一种数加一,也就是它们优先操作第一种数后,那么第一个人会除掉\(\lceil \frac{sum1}{2} \rceil\)次,第二个可以使得\(\lfloor \frac{sum1}{2} \rfloor\)个这类数回合加一。然后剩下的操作就都可以给第二种数了,可以增加\(\min(sum - \lceil \frac{sum1}{2} \rceil, sum2)\)回合。

点击查看代码
#include <bits/stdc++.h>

using i64 = long long;

void solve() {
	int n, q;
	std::cin >> n >> q;
	std::vector<i64> a(n);
	for (int i = 0; i < n; ++ i) {
		std::cin >> a[i];
	}

	std::vector<i64> sum(n + 1), sum1(n + 1), sum2(n + 1);
	for (int i = 0; i < n; ++ i) {
		sum[i + 1] = sum[i] + std::__lg(a[i]);
		sum1[i + 1] = sum1[i];
		sum2[i + 1] = sum2[i];
		int f = 0;
		int x = a[i] / 2;
		while (x > 1) {
			x += 1;
			f |= (1ll << std::__lg(x)) == (x);
			x /= 2;
		}
		sum2[i + 1] += f;
		if (f) {
			continue;
		}
		x = a[i];
		f = 0;
		while (x > 1) {
			x += 1;
			f |= (1ll << std::__lg(x)) == (x);
			x /= 2;
		}
		sum1[i + 1] += f;
	}


	while (q -- ) {
		int l, r;
		std::cin >> l >> r;
		i64 v = sum[r] - sum[l - 1];
		i64 ans = v;
		i64 s = sum1[r] - sum1[l - 1];
		v -= (s + 1) / 2;
		ans += s / 2;
		ans += std::min(v, sum2[r] - sum2[l - 1]);
		std::cout << ans << "\n";
	}
}

int main() {
	std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
	int t = 1;
	std::cin >> t;
	while (t -- ) {
		solve();
	}
	return 0;
}

E. Monotone Subsequence

题意:交互题。有一个\(n\),和一个隐藏的长度为\(n^2+1\)的排列\(p\)。你每次可以问一个子序列,然后会回答你有哪些位置是前缀最大的数。最多问\(n\)次,求一个长度为\(n+1\)的单调子序列。

一开始全问,然后每次把上一次得到的序列删掉继续问。中间有长度大于等于\(n+1\)的直接输出。如果直接没有递增大于等于\(n+1\)的序列,则一定有一个递减超过\(n+1\)的序列。因为我们可以每次根据回答得到一些位置大小关系,我们从前面的位置向后面比它小的位置连边,因为每次回答长度最多为\(n\),那么可以问满\(n\)次,因为每次把之前的回答删掉了,那么当前询问的位置前面一定有位置比它大,而这些位置后面有比他小的,然后又\(n\)次连边,这样就有\(n\)条边,就有\(n+1\)个点。只需要最后做\(dfs\)\(bfs\)求一下方案就行。

点击查看代码
#include <bits/stdc++.h>

using i64 = long long;

std::vector<int> ask(std::vector<int> a) {
	std::cout << "? " << a.size();
	for (auto & x : a) {
		std::cout << " " << x;
	}
	std::cout << std::endl;
	int k;
	std::cin >> k;
	std::vector<int> res(k);
	for (auto & x : res) {
		std::cin >> x;
	}
	return res;
}

void answer(std::vector<int> a) {
	std::cout << "!";
	for (auto & x : a) {
		std::cout << " " << x;
	}
	std::cout << std::endl;
}

void solve() {
	int n;
	std::cin >> n;
	int m = n * n + 1;
	std::vector<int> ans;
	std::set<int> s;
	for (int i = 1; i <= m; ++ i) {
		s.insert(i);
	}

	std::vector<std::vector<int>> adj(m + 1);
	std::vector<int> in(m + 1);
	for (int i = 0; s.size() > 0 && i < n && ans.size() < n + 1; ++ i) {
		std::vector<int> b(s.begin(), s.end());
		ans = ask(b);
		for (int i = 0; i < ans.size(); ++ i) {
			int j = i + 1 < ans.size() ? ans[i + 1] : m + 1;
			for (int k = ans[i] + 1; k < j; ++ k) {
				if (s.count(k)) {
					adj[ans[i]].push_back(k);
					++ in[k];
				}
			}
		}

		for (auto & x : ans) {
			s.erase(x);
		}
	}

	if (ans.size() >= n + 1) {
		while (ans.size() > n + 1) {
			ans.pop_back();
		}
		answer(ans);
	} else {
		ans.clear();
		std::queue<int> q;
		std::vector<int> d(m + 1), pre(m + 1);
		for (int i = 1; i <= m; ++ i) {
			if (in[i] == 0) {
				q.push(i);
				d[i] = 1;
			}
		}

		while (q.size()) {
			int u = q.front(); q.pop();
			for (auto & v : adj[u]) {
				if (d[v] < d[u] + 1) {
					d[v] = d[u] + 1;
					pre[v] = u;
				}

				if ( -- in[v] == 0) {
					q.push(v);
				}
			}
		}

		for (int i = 1; i <= m; ++ i) {
			if (d[i] == n + 1) {
				int x = i;
				while (x) {
					ans.push_back(x);
					x = pre[x];
				}
				break;
			}
		}

		if (ans.size() == 0) {
			ans.push_back(0);
		}
		std::ranges::reverse(ans);
		answer(ans);
	}
} 

int main() {
	std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
	int t = 1;
	std::cin >> t;
	while (t -- ) {
		solve();
	}
	return 0;
}
posted @ 2025-10-04 14:10  maburb  阅读(164)  评论(0)    收藏  举报