Codeforces Round 1061 (Div. 2)
A. Pizza Time
题意:有\(n\)个物品,每次分成三部分,你拿走最少的一部分,第二大的丢弃,最大的保留下一轮。求你最多可以拿到多少。
我们可以分\(1, 1, n - 2\)。这样我们得到\(\lfloor \frac{n-3}{2} \rfloor\)个,然后剩下\(3\)个可以拿一个。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
void solve() {
int n;
std::cin >> n;
std::cout << (n - 3) / 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. Strange MachineB. Strange Machine
题意:有\(n\)个指令,如果指令是\(A\)就把当前数减一,如果是\(B\)就把当前数除二向下取整,这些指令循环执行。\(q\)次查询求把一个数变成\(0\)的操作数。
如果有除二的指令,则最多执行\(log\)轮,直接模拟即可。
如果没有,则只有减一的指令,答案就是这个数本身。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
void solve() {
int n, q;
std::cin >> n >> q;
std::string s;
std::cin >> s;
int cntb = std::ranges::count(s, 'B');
while (q -- ) {
int a;
std::cin >> a;
if (cntb == 0) {
std::cout << a << "\n";
} else {
int ans = 0;
for (int i = 0; a ; i = (i + 1) % n) {
if (s[i] == 'A') {
-- a;
} else {
a /= 2;
}
++ ans;
}
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. Maximum GCD on Whiteboard
题意:有\(n\)个数,你可以删除最多\(k\)个。对于剩下的数你可以进行以下操作:把这个数分成三个数,这三个的和等于这个数,然后把最大最小的两个数加入,删除当前数。求可以达到达到全局\(gcd\)最大是多少。
观察一下什么数可以分出来两个都有因子\(d\)的数,这个数要么是\(d\)的倍数,可以不操作;否则设这个数是\(x\),则可以分成这三个数:\(d, d + x\% d, x - d - x \% d\)。那么要求这个数大于等于\(4d\)。那么可以计算\([d, 4d]\)中有多少数不是\(d\)的倍数,这些数也要删去。
则可以枚举答案,看需要删去的数的个数是不是小于等于\(k\)。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
void solve() {
int n, k;
std::cin >> n >> k;
std::vector<int> sum(2 * n + 1);
for (int i = 0; i < n; ++ i) {
int x;
std::cin >> x;
++ sum[x];
}
for (int i = 1; i <= 2 * n; ++ i) {
sum[i] += sum[i - 1];
}
for (int i = n; i >= 1; -- i) {
int cnt = sum[i - 1];
if (i * 2 <= 2 * n) {
cnt += sum[i * 2 - 1] - sum[i];
}
if (i * 3 <= 2 * n) {
cnt += sum[i * 3 - 1] - sum[i * 2];
}
if (i * 4 <= 2 * n) {
cnt += sum[i * 4 - 1] - sum[i * 3];
}
if (cnt <= k) {
std::cout << i << "\n";
return;
}
}
}
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. Find the Last NumberD. Find the Last Number
题意:交互题。有一个隐藏排列,你每次可以让\(p_i(i<n)\)和一个数进行按位与操作,如果结果不为\(0\)则返回\(1\),则返回\(0\)。最多\(2n\)次询问求出\(p_n\)。
可以按位问,计算出这一位在剩下的数里有多少数是\(1\)多少是\(0\),然后把通过查询所有剩下的数可以知道\(p_n\)这一位是什么,那么其它数可以丢弃,剩下的数进行下一位的操作。这样每一位操作后剩下的数都会减半,最终询问数不超过\(2n\)。这里口胡一下证明,大概就是一开始\([1, n]\)里第\(0\)位是\(1\)的个数和是\(0\)的个数相差不超过\(1\),然后我们选择其中一个集合后,因为里面第\(0\)位都相同那么可以都除一个\(2\)再看第\(0\)位,这样相当于把\([1, n]\)放缩到了\([1, \frac{n}{2}]\),然后继续看第\(0\)位,那么显然差距也不超过\(1\)。那么这样从小到大按位做大概每次剩下的数会减半。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
int ask(int i, int x) {
std::cout << "? " << i << " " << x << std::endl;
int res;
std::cin >> res;
return res;
}
void solve() {
int n;
std::cin >> n;
int m = 0;
for (int i = 20; i >= 0; -- i) {
if (n >> i & 1) {
m = i;
break;
}
}
int f[20][2]{};
for (int i = 1; i <= n; ++ i) {
for (int j = 0; j <= m; ++ j) {
f[j][i >> j & 1] += 1;
}
}
std::vector<int> a;
for (int i = 1; i < n; ++ i) {
a.push_back(i);
}
auto b = a;
b.push_back(n);
int tot = 0;
std::vector<int> st(n + 1);
for (int i = 0; i <= m && b.size() > 1; ++ i) {
std::vector<int> na[2];
for (auto & x : a) {
na[ask(x, 1 << i)].push_back(x);
}
if (na[0].size() < f[i][0]) {
a = na[0];
std::vector<int> nb;
for (auto & x : b) {
if (x >> i & 1) {
st[x] = 1;
for (int j = i + 1; j <= m; ++ j) {
-- f[j][x >> j & 1];
}
} else {
nb.push_back(x);
}
}
b = nb;
} else {
a = na[1];
std::vector<int> nb;
for (auto & x : b) {
if (x >> i & 1) {
nb.push_back(x);
} else {
st[x] = 1;
for (int j = i + 1; j <= m; ++ j) {
-- f[j][x >> j & 1];
}
}
}
b = nb;
}
}
for (int i = 1; i <= n; ++ i) {
if (!st[i]) {
std::cout << "! " << i << std::endl;
return;
}
}
}
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. Best Time to Buy and Sell Stock
题意:一个数组,两个人博弈。第一个人每次拿走一个数,另一个每次选择一个数使得第一个人后面无法拿走这个数。最后数组的价值为\(\max a_j - a_i(j > i)\)。第一个人希望价值低,第二个人希望价值高,求最后价值。
考虑二分答案。
那么对于每个\(i\),可以对后面每个\(a_j \geq a_i + x\)的\(j\)连边。那么如果第二个人可以选择到一条边的两个点,就\(ok\)。
考虑怎样的图符合这个条件,考虑一个连通块的点数,如果点数为\(2\),对于第二个人,如果操作其中一个点,第一个人会立即操作另一个点,所以点数为\(2\)的连通块其实没有用。如果连通块点数大于等于\(3\),那么如果第一个人如果不管怎么操作都会留下一个点数大于等于\(3\)的连通块,那么第二个人可以选择一个度数大于等于\(2\)的点就赢了。
对于点数小于\(6\)的连通块,我们暴力判断,对于大于等于\(6\)的连通块,第一个人肯定选择其中一个度数最大的点删,那么就可以判断其它点剩下的度数有没有大于等于\(2\)的。因为第一个人删去一个点只会使得其相邻点度数减一,那么如果不选择度数最大的点,剩下最大度数最多减一,不是最优解。那么如果有最大度数的只有一个点,肯定就选择这个点;如果有多个点,记最大度数为\(d\),那么希望能把其它度数也为\(d\)的度数都减一,那么\(d \geq 3\)的情况就无解了,因为其它度数为\(d\)的点减一后度数也大于等于\(2\)。那么对于最大度数都为\(2\)的情况,也就是一条链,显然当点数大于等于\(6\)时无解。
点击查看代码
#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];
}
auto check = [&](int x) -> bool {
std::vector<std::vector<int>> adj(n);
std::set<std::pair<int, int>> s;
for (int i = n - 1; i >= 0; -- i) {
auto it = s.end();
for (int j = 0; j < 3 && it != s.begin(); ++ j) {
-- it;
if (a[i] + x <= it->first) {
adj[i].push_back(it->second);
adj[it->second].push_back(i);
} else {
break;
}
}
s.emplace(a[i], i);
while (s.size() > 3) {
s.erase(s.begin());
}
}
int tot = 0;
std::vector<int> vis(n);
for (int i = 0; i < n; ++ i) {
if (vis[i]) {
continue;
}
std::vector<int> a;
int U = -1;
auto dfs = [&](auto && self, int u) -> void {
vis[u] = 1;
a.push_back(u);
if (U == -1 || adj[u].size() > adj[U].size()) {
U = u;
}
for (auto & v : adj[u]) {
if (!vis[v]) {
self(self, v);
}
}
};
dfs(dfs, i);
if (a.size() > 2) {
if ( ++ tot >= 2) {
return true;
}
if (a.size() >= 6) {
for (auto & u : a) {
if (u == U) {
continue;
}
int t = 0;
for (auto & v : adj[u]) {
if (v == U) {
t = 1;
break;
}
}
if ((int)adj[u].size() - t >= 2) {
return true;
}
}
} else {
auto b = a;
bool ok = true;
for (auto & u : b) {
for (auto & v : b) {
vis[v] = 0;
}
vis[u] = 1;
bool flag = false;
for (auto & v : b) {
if (vis[v]) {
continue;
}
a.clear();
dfs(dfs, v);
flag |= a.size() >= 3;
}
ok &= flag;
}
if (ok) {
return true;
}
}
}
}
return false;
};
int lo = -1e9, hi = 1e9;
while (lo < hi) {
int mid = lo + hi + 1 >> 1;
if (check(mid)) {
lo = mid;
} else {
hi = mid - 1;
}
}
std::cout << lo << "\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;
}
F1. Strange Operation (Easy Version)
题意:一个排列,你每次可以选择三个位置\(i, j, k\),满足\(i < j < k\),且\(p_i, p_j, p_j\)是连续的三个数,\(p_i > p_j, p_i > p_k\)。然后使得\(p_i - 2, p_j + 1, p_k + 1\)。求可以得到的最小字典序。
从小到大操作,如此反复直到没有可以操作的位置。
对于\(i\)来说,我们希望他和\(i+1, i+2\)操作,这样\(i\)就会换到更前面,这样字典序就会更小。从小到大操作,操作\(i\)后,\(i+2\)可能继续操作换到\(i, i+1\)前面,这样\(i\)又能往前面走。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
void solve() {
int n;
std::cin >> n;
std::vector<int> a(n + 1), p(n + 1);
for (int i = 1; i <= n; ++ i) {
std::cin >> a[i];
p[a[i]] = i;
}
while (true) {
bool flag = false;
for (int i = 1; i <= n - 2; ++ i) {
if (p[i + 2] < p[i + 1] && p[i + 2] < p[i]) {
int x = p[i], y = p[i + 1], z = p[i + 2];
a[x] = i + 1;
a[y] = i + 2;
a[z] = i;
p[i] = z;
p[i + 1] = x;
p[i + 2] = y;
flag = true;
}
}
if (!flag) {
break;
}
}
for (int i = 1; i <= n; ++ i) {
std::cout << a[i] << " \n"[i == 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;
}

浙公网安备 33010602011771号