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