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;
}