2023.11.22 比赛记录 题解
传送门 | 阅读 pdf
A
好渴鹅本来用的贪心,但是打完之后—— 文明 的,竟然样例都过不了!原来是一只累加了负数,然后就吃席了。增加环节改成取 \(\max\)。我爱你 的,竟然又 WA 了。究竟是神么错了,一定有那个地方忽略了,一定有,一定,一……
哦哦哦哦哦哦——原来是如果没有取当前的元素,那么后面就不能接上了。可爱 的,这题根本做不了啊啊。怎么办呢?哇哇哇,原来可以使用一个阳光开朗的 \(f\) 数组,然后每次都取最优的,存进数组。这不就成了吗?测阳寿例!我 文明 的,怎么又双 WA 了?
哦哦哦,原来是因为 \(f\) 数组,没有出逝化,改一下就可以惹。然后就可以过掉了,但是可以不用数组只是本渴鹅过于懒,不想多写。
#include <iostream>
using namespace std;
const int kMaxN = 2e5 + 5;
int a[kMaxN], dp[kMaxN], t, n, ans;
int main() {
for (cin >> t; t; t--) {
cin >> n, ans = -1e9;
fill(dp + 1, dp + n + 1, 0);
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
if (n == 1) {
cout << a[1] << '\n';
continue;
}
for (int i = 1; i <= n; i++) {
if (i == 1 || abs(a[i]) % 2 != abs(a[i - 1]) % 2) {
dp[i] = max(dp[i - 1] + a[i], a[i]);
} else {
dp[i] = a[i];
}
ans = max(ans, dp[i]);
}
cout << ans << '\n';
}
return 0;
}
B
一看到题目——这也忒简单了吧?虽然看着很向博弈论,额本来就是,但是还是灰常的简单的。既然瓦尼娅每次都会往 \(3\) 的倍数去前进,而沃瓦总是会给她搞回去,让瓦尼娅一夜回到解放前。所以瓦尼娅的数字离最近的 \(3\) 的倍数的绝对值只能是 \(1\),因为瓦尼娅改完后沃瓦就会直接给她干回去。所以直接使用一个根根淡淡的 if 就行了,这道题目我只能说非常的炸裂。
#include <iostream>
using namespace std;
int t, n;
int main() {
for (cin >> t; t; t--) {
cin >> n;
if ((n + 1) % 3 == 0 || (n - 1) % 3 == 0) {
cout << "First\n";
} else {
cout << "Second\n";
}
}
return 0;
}
C
一看题目——哦哟哟,居然要在 \(\mathcal O(n)\) 的时间复杂度之内求出答案也~但是我们最简单的算法都要 \(n^2\) 啊。区间求和就可以使用大名鼎鼎的线段树前缀和啦,这样子单次时间复杂度就是 \(\mathcal O(\log_2 n)\) 啊不 \(\mathcal O(1)\)。然后我们枚举因数需要 \(n\) 次循环,这个貌似没法避免,然后里面一个 \(\mathcal O(n)\) 的循环统计答案。好像需要 \(\mathcal O(n^2)\) 耶。怎么办呢?怎么办?怎么?怎?
哦哦哦哦,由于枚举出了第一个因数,那么第二个因数就可以算出了。所以只要枚举到 \(i\le n\div i\) 就行了,也就是 \(1\le \sqrt{n}\)。由于我们使用 sqrt 函数的话速度会非常 man~所以我们直接算 \(i^2\le n\) 就行了。这样子,时间复杂度就被我们压缩到了 \(\mathcal O(n\sqrt{n})\) 了。貌似好像仿佛还是过不了耶。
哇哇哇哇,我们知道一个数 \(n\) 的因数大约有 \(\log_2 n\) 个,所以我们预处理出所有的因数,然后再统一进行答案的累加。这样子,我们的时间复杂度就被压缩成了 \(\mathcal O(n\log_2 n)\)。整整 \(2\) 秒的时限本渴鹅只能说是绰绰有余!
#include <iostream>
#include <vector>
using namespace std;
using ll = long long;
const ll kMaxN = 1.5e5 + 5;
ll a[kMaxN], p[kMaxN], t, n, ans;
vector<ll> v;
ll sum(ll l, ll r) {
return p[r] - p[l - 1];
}
int main() {
for (cin >> t; t; t--) {
cin >> n, v.clear(), ans = 0;
for (ll i = 1; i <= n; i++) {
cin >> a[i];
p[i] = p[i - 1] + a[i];
}
for (ll i = 1; i <= n; i++) {
if (n % i == 0) {
v.push_back(i);
}
}
for (ll i : v) {
ll mn = 1e18, mx = 0;
for (ll j = 1; i * j <= n; j++) {
ll x = sum((j - 1) * i + 1, j * i);
mn = min(mn, x);
mx = max(mx, x);
}
ans = max(ans, mx - mn);
}
cout << ans << '\n';
}
return 0;
}
D
首先看到题目——哇,这么长的题面,这么难的数论题怎么做啊?别急,我先缕一缕题意:\(n\) 个数,第 \(i\) 个数就是 \(a_i\),而 \(b_i=2^{a_i}\)。你需要找出一对 \((i,j)\),使得 \(i<j\) 并且 \(b_i^{b_j}=b_j^{b_i}\),也就是 \(\prod\limits_{i=1}^{b_j}b_i=\prod\limits_{i=1}^{b_i}a_j\),说人话就是 \(b_i\) 的 \(b_j\) 次方等于 \(b_j\) 的 \(b_i\) 次方。
首先我们知道,如果底数和次数是同一个数,那么他们就是可以任意交换的。通过排列公式 \(A_n^m=\cfrac{n!}{(n-m)!}\),我们就可以知道有多少种排列。由于这题只需要选择两个数,因此假设一个数的出现次数是 \(x\),我们就可以推导出 \(x\) 个数中选择 \(2\) 个数,方案有:\(1+2+\dots+x-1\),也就是 \(\sum\limits_{i=1}^{x-1}i\)。通过等差数列的公式,我们知道这个值其实就可以算出来:\(\cfrac{x\times(x-1)}{2}\)。
然后就是解决是否有两个不同的数交换后,乘方运算仍然相等了。通过一个测试程序,我们知道了好像只有 \(2\) 和 \(4\) 可以。所以,毫不犹豫地将代码打完,提交上去——AC!!!看来只有 \(2\) 和 \(4\) 呢。
#include <iostream>
#include <map>
using namespace std;
using ll = long long;
ll t, n, ans;
map<ll, ll> f;
ll sum(ll l, ll r) {
return (l + r) * (r - l + 1) / 2;
}
int main() {
for (cin >> t; t; t--) {
cin >> n, f.clear(), ans = 0;
for (ll i = 1, x; i <= n; i++) {
cin >> x;
f[x]++;
}
for (auto i : f) {
ans += sum(1, i.second - 1);
}
cout << ans + f[1] * f[2] << '\n';
}
return 0;
}
E
一看——这不就是大爆搜吗?写上一个 dfs,才发现根本就不好写,原因如下:
- 可以搬着宝箱走,而且还有 \(k\) 的限制;
- 可以中途放下宝箱,然后再去找钥匙。
所以我们会感觉不好写。其实非常简单,一共就那么几种情况:
-
拿钥匙去打开宝箱:
- 首先移到钥匙那里;
- 到宝箱的位置打开。
-
先拿宝箱再去找钥匙:
- 拿上宝箱;
- 走到离钥匙最近的位置
- 看一下钥匙在哪里
- 如果钥匙在右边,就走到右边回来
- 否则就走到左边再回去
- 记录答案
取最小值就行惹。
#include <iostream>
using namespace std;
int t, gld, key, k;
int solve1() { // 捡钥匙再开门
int x = 0, s = 0;
s += abs(key - x), x = key;
s += abs(gld - x), x = gld;
return s;
}
int solve2() { // 搬上宝箱去捡钥匙
int x = 0, s = 0;
s += abs(gld - x), x = gld;
if (x < key) { // 宝藏在右边
s += min(x + k, key) - x, x = min(x + k, key); // 离钥匙最近
} else {
s += x - max(x - k, key), x = max(x - k, key);
}
s += abs(key - x) * 2; // 来回捡钥匙
return s;
}
int main() {
for (cin >> t; t; t--) {
cin >> gld >> key >> k;
cout << min(solve1(), solve2()) << '\n';
}
return 0;
}
F
首先,我们看到题目——排序。额……总不可能排完序就 A 了吧?原来这是一种灰常炸裂的排序,不仅排序速度极慢,而且有时候 可爱 还根本就不排出来!如果手动模拟的话,最坏复杂度很有可能是 \(\mathcal O(n^2)\) 的,因为每一个数都转了一圈。所以我们就只能找规律了。
我们拿出样例中可以排出来和不能排出来的两个序列,然后进行模拟:
可以看到第 \(2\) 个矩阵数字 \(1\) 排在了最前面,而数组还没有拍完序,此时移动 \(1\) 的话它还是会回到原位。仔细观察两个数列,我们发现了后面几个数是有序的,而最小值正好放到了后面几个有序的数列的里面或者正好在左边一个位置。这个规律十分玄学,但是提交上去可以 AC。
#include <iostream>
using namespace std;
const int kMaxN = 2e5 + 5;
int a[kMaxN], t, n, id, x;
int main() {
for (cin >> t; t; t--) {
cin >> n, id = 1;
for (int i = 1; i <= n; i++) {
cin >> a[i];
if (a[i] < a[id]) {
id = i;
}
}
for (x = n; x > 1 && a[x] >= a[x - 1]; x--) { }
if (id >= x - 1) {
cout << x - 1 << '\n';
} else {
cout << "-1\n";
}
}
return 0;
}
G
我们知道其实 \(x\) 和 \(y\) 是可以单独拆出来进行计算的。所以我们直接排序,然后把前 \(n\) 个数当作 \(x\) 坐标,后 \(n\) 个数就是 \(y\) 坐标。那你可能会问:前面的差会比后面小很多啊。但是这题求的是曼哈顿距离,因此 \(x\) 和 \(y\) 是可以颠倒的,所以拍完序一通乱搞就行了。
#include <iostream>
#include <algorithm>
using namespace std;
const int kMaxN = 105;
int a[2 * kMaxN], t, n, ans;
int main() {
for (cin >> t; t; t--) {
cin >> n, ans = 0;
for (int i = 1; i <= 2 * n; i++) {
cin >> a[i];
}
sort(a + 1, a + 2 * n + 1);
for (int i = 2; i <= n; i++) {
ans += (a[i] - a[i - 1]) + (a[n + i] - a[n + i - 1]);
}
cout << ans << '\n';
cout << a[1] << ' ' << a[n + 1] << '\n';
for (int i = 2; i <= n; i++) {
cout << a[i] << ' ' << a[n + i] << '\n';
}
}
return 0;
}

浙公网安备 33010602011771号