Codeforces Round 1040 (Div. 2)
A. Submission is All You Need
题意:给你\(n\)个数,每次选一些数出来,获得它们的和或者\(mex\),然后把它们删掉。求最大价值。
显然除了单独一个\(0\)拿出来选\(mex\),其它直接一次拿出来选和最优。
点击查看代码
#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];
if (a[i] == 0) {
++ a[i];
}
}
std::cout << std::accumulate(a.begin(), a.end(), 0) << "\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. Pathless
题意:一个只包含\(0, 1, 2\)的数组,一个人从\(1\)开始走,每次可以往左也可以往右,走到\(n\)算结束。这个人希望走过的数的总和为\(s\)。你可以重新排列数组,求能不能无法达到\(s\)。
分类讨论。记\(sum\)为数组总和。
如果\(sum > s\),随便摆。
如果\(sum = s\),无解。
如果\(sum < s\),如果\(s - sum\)是偶数,因为想要增大和一定是在两个数来回摇摆,那么希望他加不出来偶数,但这是不可能的,因为肯定有一个\(0\)要么和\(1\)相邻要么和\(2\)相邻,\(01\)来回偶数次或者\(02\)来回任意次都可以凑出想要的偶数。 如果\(s-sum\)是奇数,那么只有\(s-sum=1\)才有解,此时把所有\(0\)放左边,\(2\)放中间,\(1\)放右边就行。否则无解,因为不管是\(01, 12\)还是\(02, 12\)都可以凑出任意大于\(1\)的奇数。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
void solve() {
int n, s;
std::cin >> n >> s;
std::vector<int> a(n);
for (int i = 0; i < n; ++ i) {
std::cin >> a[i];
}
int cnt0 = std::ranges::count(a, 0);
int cnt1 = std::ranges::count(a, 1);
int cnt2 = std::ranges::count(a, 2);
int sum = std::accumulate(a.begin(), a.end(), 0);
if (sum > s) {
for (int i = 0; i < n; ++ i) {
std::cout << a[i] << " \n"[i == n - 1];
}
} else if (sum == s) {
std::cout << -1 << "\n";
} else {
if (s - sum & 1) {
if ((s - sum) % 3 != 0) {
if (s - sum == 1) {
a.clear();
for (int i = 0; i < cnt0; ++ i) {
a.push_back(0);
}
for (int i = 0; i < cnt2; ++ i) {
a.push_back(2);
}
for (int i = 0; i < cnt1; ++ i) {
a.push_back(1);
}
for (int i = 0; i < n; ++ i) {
std::cout << a[i] << " \n"[i == n - 1];
}
} else {
std::cout << -1 << "\n";
}
} else {
std::cout << -1 << "\n";
}
} else {
std::cout << -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;
}
C. Double Perspective
题意:一些数对\((a_i, b_i)\),价值为所有数对看作区间的并集大小减去所有数对看作连边的图点所有环的大小。求最大价值。
记\(R[i]\)为\(i\)为左端点往右可以到的最右的位置,\(id[i]\)表示这个最右位置对应的数对编号。
那么直接选所有\(id[i]\)就行了。因为不会有环,且并集最大。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
void solve() {
int n;
std::cin >> n;
std::vector<int> R(2 * n + 1), id(2 * n + 1, -1);
for (int i = 0; i < n; ++ i) {
int l, r;
std::cin >> l >> r;
if (r > R[l]) {
R[l] = r;
id[l] = i;
}
}
std::vector<int> ans;
for (int i = 1; i <= 2 * n; ++ i) {
if (id[i] != -1) {
ans.push_back(id[i]);
}
}
std::cout << ans.size() << "\n";
for (auto & i : ans) {
std::cout << i + 1 << " \n"[i == ans.back()];
}
}
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. Stay or Mirror
题意:一个排列,你可以把一些位置变成\(2n - p_i\)。求最小逆序对。
考虑按数从小到大考虑,直接计算不变的时候产生的逆序对和变化后产生的逆序对,如果变化后逆序对更少,则直接变。
越小的数变化后越大,如果选择变化,那么这个数前面的位置不管变不变都不会和它产生逆序对,它后面的位置不管变不变一定和它产生逆序对,也就是后面的数的选择不影响当前数的选择,所以变化后更小就直接变就行了。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
void solve() {
int n;
std::cin >> n;
std::vector<int> a(n + 1);
for (int i = 1; i <= n; ++ i) {
std::cin >> a[i];
}
std::vector<int> id(n + 1);
for (int i = 1; i <= n; ++ i) {
id[a[i]] = i;
}
for (int i = 1; i <= n; ++ i) {
int p = id[i];
int sum1 = 0;
for (int j = 1; j < p; ++ j) {
sum1 += a[j] > a[p];
}
for (int j = p + 1; j <= n; ++ j) {
sum1 += a[p] > a[j];
}
int sum2 = 0;
for (int j = 1; j < p; ++ j) {
sum2 += a[j] > 2 * n - a[p];
}
for (int j = p + 1; j <= n; ++ j) {
sum2 += 2 * n - a[p] > a[j];
}
if (sum1 > sum2) {
a[p] = 2 * n - a[p];
}
}
int ans = 0;
for (int i = 1; i <= n; ++ i) {
for (int j = i + 1; j <= n; ++ j) {
ans += a[i] > a[j];
}
}
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;
}
E1 & E2
题意:交互题。有一个你事先不知道的括号序列,你可以把任意一些位置拿出来拼成一个序列,问这个序列有多少子串是合法的。\(E1\)最多询问\(550\)次,\(E2\)最多询问\(200\)次。
ps:赛时已经想出来二分求一个左右括号,但没往二进制方向想,主要是看\(E1\)以为是一次问两个,没想出来,后来看\(E2\)限制想出来用二进制,不过这个方法也能解决\(E1\),所以我还不知道不用二进制的方法怎么解决\(E1\)
考虑二分找到一个左括号和一个右括号,每次查询\([l, mid] + [l, mid]\),也就是把一个区间接到它后面查询一次,如果这个区间既有左括号又有右括号,返回值一定不是\(0\),那么这样二分最后区间小于等于\(2\)的时候结束。然后查询区间里两个位置,如果是"()"则会返回\(1\),否则是")(会返回\(0\),于是我们就可以知道一个左括号和一个右括号的位置。
然后考虑八个八个查询,我们用\(2^i\)表示一个位置是不是左括号,具体就是\(s_p + ))\)的形式问\(2^i\)次,如果这个位置是左括号,那么二进制下第\(i\)为\(0\),否则为\(1\),因为有两个右括号挡在中间,所以每个位置互不影响。我们一次可以询问八个数,分别用不同的八位表示,一次长度是\(2^8 \times 3\)。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
int ask(std::vector<int> & a) {
int n = a.size();
std::cout << "? " << n;
for (auto & x : a) {
std::cout << " " << x;
}
std::cout << std::endl;
int res;
std::cin >> res;
return res;
}
void solve() {
int n;
std::cin >> n;
int l = 1, r = n;
while (l + 1 < r) {
int mid = l + r >> 1;
std::vector<int> a(mid - l + 1);
std::ranges::iota(a, l);
a.insert(a.end(), a.begin(), a.end());
if (ask(a)) {
r = mid;
} else {
l = mid;
}
}
int p1 = l, p2 = r;
if (l == r) {
if (l < n) {
p2 = l + 1;
} else {
p2 = l;
p1 = l - 1;
}
}
{
std::cout << "? " << 2 << " " << p1 << " " << p2 << std::endl;
int res;
std::cin >> res;
if (!res) {
std::swap(p1, p2);
}
}
int p = p2;
std::string s(n, '?');
auto get = [&](int l, int r) -> void {
std::vector<int> q;
for (int i = l; i <= r; ++ i) {
int t = i - l;
for (int j = 0; j < 1 << t; ++ j) {
q.push_back(i);
q.push_back(p);
q.push_back(p);
}
}
int x = ask(q);
for (int i = 0; i <= r - l; ++ i) {
if (x >> i & 1) {
s[l + i - 1] = '(';
} else {
s[l + i - 1] = ')';
}
}
};
for (int i = 1; i <= n; i += 8) {
get(i, std::min(n, i + 7));
}
std::cout << "! " << s << std::endl;
}
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;
}