A - MEX Partition
思维?
求 \(a\) 的 \(\text{mex}\)。
关于证明,参考官方题解:
首先,让 \(m=\operatorname{mex}(A)\) 。我们可以忽略所有大于 \(m\) 的元素。这是因为由于 \(m\) 是 mex, \(m\) 不会出现在 \(A\) 中,因此它也不会出现在任何分区中。因此,每个元素进入哪个分区并不重要。
现在,我们来看看数字 \(0\) 。假设在 \(A\) 中至少有一个 \(0\) 。那么,分区中至少有一个多集合必须包含 \(0\) (因为 \(A\) 中的每个元素都必须包含在分区中的一个多集合中)。这也意味着所有多集都必须包含 \(0\) ,否则所有多集的 \(\operatorname{mex}\) 就不可能相同(对某些多集来说是 \(0\) ,但对另一些多集来说大于 \(0\) )。
一旦我们得出 \(0\) 必须存在于所有分集中的结论,我们就可以对 \(1,2,\ldots,m-1\) 做出同样的结论。也就是说, \(m\) 是唯一可能的分数。
点击查看代码
#include <bits/stdc++.h>using namespace std;using i64 = long long;void solve() {int n;std::cin >> n;int cnt[102] {};for (int i = 0; i < n; i += 1) {int x;std::cin >> x;cnt[x] += 1;}int ans = 0;while (cnt[ans]) {ans += 1;}std::cout << ans << "\n";
}int main() {ios::sync_with_stdio(false);cin.tie(nullptr);int t;cin >> t;while (t--) {solve();}return 0;
}
B - Distinct Elements
差分,构造。
考虑每个数产生的贡献,记 \(b_i-b_{i-1}=x\),如果 \(x=i\),说明第 \(i\) 位置产生了 \(i\) 个贡献,即一定是前面没有出现过的数,记 \(\text{now}\) 代表出现过的最后一个数字,那么 \(ans_i=\text{now}+1\),否则一定是在前面第 \(i-x\) 个位置就出现过的数,这样才只产生了 \(x\) 的贡献,即 \(ans_i=ans_{i-x}\)。
点击查看代码
#include <bits/stdc++.h>using namespace std;using i64 = long long;void solve() {int n;std::cin >> n;std::vector<i64> b(n), a(n);for (int i = 0; i < n; i += 1) {std::cin >> b[i];a[i] = b[i];if (i) {a[i] -= b[i - 1];}}int now = 1;std::vector<int> ans(n);ans[0] = 1;for (int i = 1; i < n; i += 1) {if(a[i] == i + 1){ans[i] = ++now;}else{ans[i] = ans[i - a[i]];}}for (int i = 0; i < n; i += 1) {std::cout << ans[i] << " \n"[i + 1 == n];}}int main() {ios::sync_with_stdio(false);cin.tie(nullptr);int t;cin >> t;while (t--) {solve();}return 0;
}
C - Reverse XOR
枚举。
\(x\) 二进制翻转之后异或等于 \(n\),那么说明 \(n\) 的某位上为 \(1\) 的话,它的这一位一定与它的翻转位是不同的,同样 \(n\) 的与这一位相对的翻转位上也应该为 \(1\),说明 \(n\) 可能是以某一位为轴,然后对称的,且这一位一定是 \(0\),因为如果以某一位为轴中心且是 \(1\),那么翻转后异或应该为 \(0\);也可能是以某两位中间为轴进行对称。
检查一下从哪位(或者哪两位中间)开始对称的,最后是否能凑出 \(n\) 即可。
点击查看代码
#include <bits/stdc++.h>using namespace std;using i64 = long long;void solve() {int n;std::cin >> n;for (int i = 29; i >= 0; i -= 1) {int cnt = 0;if (~n >> i & 1) {for (int j = 1; j + i <= 60 && i - j >= 0; j += 1) {int x = n >> (i + j) & 1, y = n >> (i - j) & 1;if (x == y && x == 1) {cnt += 2;}if (x ^ y) {break;}}if (cnt == __builtin_popcount(n)) {std::cout << "YES\n";return;}}cnt = 0;for (int j = 1; j + i <= 60 && i - j + 1 >= 0; j += 1) {int x = n >> (i + j) & 1, y = n >> (i - j + 1) & 1;if (x == y && x == 1) {cnt += 2;}if (x ^ y) {break;}}if (cnt == __builtin_popcount(n)) {std::cout << "YES\n";return;}}std::cout << "NO\n";}int main() {ios::sync_with_stdio(false);cin.tie(nullptr);int t;cin >> t;while (t--) {solve();}return 0;
}
D - MAD Interactive Problem
思维。
首先可以确定的是,如果我们现在有 \(n\) 个不知道位置的值,只知道它们互不相同,那么拿这 \(n\) 个数和任意一个其他位置询问得到的答案就是这个位置的答案。
所以可以先进行 \(2n\) 次询问,维护不确定数的位置,即使前 \(n\) 个数都不相同,也能得出后 \(n\) 个数。
同理,如果我们现在有 \(n\) 个知道位置的值,并且它们互不相同,那么拿这 \(n\) 个数和任意一个其他位置询问得到的答案同样也是这个位置的答案。
由前面 \(2n\) 次得出 \(n\) 个已知的数后,再拿这 \(n\) 个数挨个去询问之前不确定的数,最多 \(n\) 次便得到全部数组。
点击查看代码
#include <bits/stdc++.h>using namespace std;using i64 = long long;void solve() {int n;std::cin >> n;auto ask = [](std::vector<int> v)->int{std::cout << "? " << v.size() << " " ;sort(v.begin(), v.end());for (auto &x : v) {std::cout << x << " ";} std::cout << std::endl;int res;std::cin >> res;return res;};auto asnwer = [](vector<int> &v)->void{std::cout << "! ";for (auto &x : v) {std::cout << x << " ";} std::cout << std::endl;};std::vector<int> ans(2 * n), pos(n), has, exit;has.push_back(1);for (int i = 1; i < 2 * n; i += 1) {has.push_back(i + 1);if (has.size() == 1) {continue;} else {auto res = ask(has);if (res) {ans[i] = res;has.pop_back();if (has.size() == 1) {ans[has.back() - 1] = res;has.pop_back();} else {exit.push_back(i + 1);}}}}for (auto &x : has) {exit.push_back(x);auto res = ask(exit);ans[x - 1] = res;exit.pop_back();}asnwer(ans);
}int main() {ios::sync_with_stdio(false);cin.tie(nullptr);int t;cin >> t;while (t--) {solve();}return 0;
}
E - Rectangles
思维。
考虑枚举 \(u,d\) 两行,把两行有 \(1\) 的列标记,那么当前 \(d\) 行当前列有 \(1\) 时构成的最小矩阵就应该是它的前一个 \(1\),记当前列为 \(i\),上一个 \(1\) 所在的列为 \(lst\),那么在 \(d\) 行 \([lst,i]\) 的区间内的答案就是 \((d-u+1)\times (i-lst+1)\),此时不必着急对 \(u\sim d-1\) 的答案进行更新,先管每一行的,枚举 \(d\) 算完 \(u+1\sim n\) 后从 \(n\) 开始到 \(u+1\) 行的每一列取后缀最小值即可更新第 \(u\) 行开头的矩阵贡献的答案。
这样的复杂度是 \(O(n^2m)\) 的,显然 \(n\) 大的时候不可取,可以发现,有 \(\min(n,m)\le \sqrt{nm}\),所以当 \(n\) 大的时候可以反过来枚举列,这样就能保证复杂度为 \(O(nm\min(n,m))\) 即 \(O(nm\sqrt{nm})\)。
点击查看代码
#include <bits/stdc++.h>using i64 = long long;void solve() {int n, m;std::cin >> n >> m;std::vector a(n, std::vector<int>(m));for(int i = 0; i < n; i += 1) {std::string s;std::cin >> s;for(int j = 0; j < m; j += 1) {a[i][j] = s[j] - '0';}}const int inf = 1E9;auto swap = [&](std::vector<std::vector<int>> &a)->void {std::vector res(m, std::vector<int>(n, inf));for(int i = 0; i < n; i += 1) {for(int j = 0; j < m; j += 1) {res[j][i] = a[i][j];}}a = std::move(res);return ;};bool f = false;if(n > m) {f = true;swap(a);std::swap(n, m);}std::vector ans(n, std::vector<int>(m, inf));for(int u = 0; u < n; u += 1) {for(int d = u + 1; d < n; d += 1) {int lst = -1;for(int i = 0; i < m; i += 1) {if(a[u][i] && a[d][i]) {if(~lst) {int S = (d - u + 1) * (i - lst + 1);for(int j = lst; j <= i; j += 1) {ans[d][j] = std::min(ans[d][j], S);}}lst = i;}}}for(int i = n - 2; i >= u; i -= 1) {for(int j = 0; j < m; j += 1) {ans[i][j] = std::min(ans[i][j], ans[i + 1][j]);}}}if(f) {swap(ans);std::swap(n, m);}for(int i = 0; i < n; i += 1) {for(int j = 0; j < m; j += 1) {if(ans[i][j] == inf) {ans[i][j] = 0;}std::cout << ans[i][j] << " \n"[j + 1 == m];}}}
int main() {std::ios::sync_with_stdio(false);std::cin.tie(nullptr);int t;std::cin >> t;while (t--) {solve();}return 0;
}