2023.12.13 秋季训练五

阅读 pdf,观感更上一层楼


水题环节

A - Short Sort

题目大意

\(3\) 张卡片,上面写着 \(\texttt{a,b,c}\),以任意排列的形式出现在一行中,你可以进行一次交换操作,问你经过操作之后,能否使这三张卡片的顺序正好变为 \(\texttt{abc}\)

思路

这题非常简单,好渴鹅这里的写法可能代码有点长,但是思路十分简单,就是不断 swap,然后看一看是否满足要求,如果都不能满足那么就输出无解。

代码

#include <iostream>

using namespace std;

int t = 1;
char a, b, c;

void solve() {
  cin >> a >> b >> c;
  if (a == 'a' && b == 'b' && c == 'c') {
    cout << "YES\n";
    return;
  }
  swap(a, b);
  if (a == 'a' && b == 'b' && c == 'c') {
    cout << "YES\n";
    return;
  }
  swap(a, b);
  swap(a, c);
  if (a == 'a' && b == 'b' && c == 'c') {
    cout << "YES\n";
    return;
  }
  swap(a, c);
  swap(b, c);
  if (a == 'a' && b == 'b' && c == 'c') {
    cout << "YES\n";
    return;
  }
  swap(b, c);
  cout << "NO\n";
}

int main() {
  for (cin >> t; t; t--) {
    solve();
  }
  return 0;
}

C - Good Kid

题目大意

有一个长度为 \(n\) 的正整数序列 \(a\),你可以将其中的一个整数加上 \(1\),然后问你加完之后的最大乘积是多少。
\(1\le n\le 9,0\le a_i\le 9\)

思路

这道题目的数据非常非常水,我们可以枚举更改的数 \(a_i\)。对于每一个 \(i\),我们都用一个 \(\mathcal O(n)\) 的循环计算值,然后在所有 \(i\) 的答案中取上最大值即可。

代码

#include <iostream>

using namespace std;
using ll = long long;

const ll kMaxN = 1e5 + 5;

ll a[kMaxN], p[kMaxN] = {1}, t = 1, n;

void solve() {
  cin >> n;
  for (ll i = 1; i <= n; i++) {
    cin >> a[i];
  }
  ll ans = 0;
  for (ll i = 1; i <= n; i++) {
    ll x = 1;
    for (ll j = 1; j <= n; j++) {
      if (j == i) {
        x *= a[j] + 1;
      } else {
        x *= a[j];
      }
    }
    ans = max(ans, x);
  }
  cout << ans << '\n';
}

int main() {
  for (cin >> t; t; t--) {
    solve();
  }
  return 0;
}

D - Target Practice

题目大意

有一个 \(10\times 10\) 的靶子,告诉你了这个靶子的射箭情况,如果一个格子的值为 X,那么就说明这个格子被射到了。射到不同的格子上,分数也不相同。你需要计算总分。

示意图

思路

这题也太炸裂了吧?其实最难的部分就是如何计算一个格子是多少分。我们先枚举 \(i\),表示假设这个格子的得分为 \(i\),然后再在 \([i,n-i+1]\) 的范围内枚举 \(j\),可以在图上找到这个规律。那么如果点 \((x,y)\)\((i,j),(x-i+1,j),(j,i),(j,n-i+1)\) 上,那么这个点的值就是 \(i\)。然后枚举即可。

代码

#include <iostream>

using namespace std;
using ll = long long;

const ll kMaxN = 1005;

ll t = 1, n = 10;
char a[kMaxN][kMaxN];

int calc(int x, int y) {
  for (int i = 1; i <= 5; i++) {
    for (int j = 1 + i - 1; j <= n - i + 1; j++) {
      if (x == i && y == j) {
        return i;
      } else if (x == n - i + 1 && y == j) {
        return i;
      } else if (x == j && y == i) {
        return i;
      } else if (x == j && y == n - i + 1) {
        return i;
      }
    }
  }
}

void solve() {
  for (int i = 1; i <= n; i++) {
    for (int j = 1; j <= n; j++) {
      cin >> a[i][j];
    }
  }
  int ans = 0;
  for (int i = 1; i <= n; i++) {
    for (int j = 1; j <= n; j++) {
      if (a[i][j] == 'X') {
        ans += calc(i, j);
      }
    }
  }
  cout << ans << '\n';
}

int main() {
  for (cin >> t; t; t--) {
    solve();
  }
  return 0;
}

正常题目环节

F - 1D Eraser

题目大意

有一个长度为 \(n\) 的彩带,上面的颜色只可能是黑 B 或白 W。给定一个 \(k\),每一次你可以把一个长度为 \(k\) 的子序列里面的颜色全部涂成白色,问你最少需要几次才能将这条彩带全部涂成白色。

思路

我们可以想出一个贪心策略。首先我们找到第一个黑色的地方,然后将这个地方以及后面的 \(k\) 个位置全部都涂成白色,因为对于任意的一个黑色的块,越在后面的时候涂成白色,那么就有可能“事半功倍”——将几个黑色的块同时涂成了白色。

因此我们枚举 \(i\),如果 \(a_i\)B,那么就将 \(i\sim i+k-1\) 内全部涂成白色。事实上,我们并不需要更改原数组,涂成白色的时候将 \(i\) 直接加上 \(k\) 就行了。

代码

#include <iostream>

using namespace std;
using ll = long long;

const ll kMaxN = 1e6 + 5;

ll t = 1, p[kMaxN], n, k;
char a[kMaxN];

void solve() {
  cin >> n >> k;
  for (int i = 1; i <= n; i++) {
    cin >> a[i];
  }
  int ans = 0;
  for (int i = 1; i <= n; ) {
    if (a[i] == 'B') {
      i += k, ans++;
    } else {
      i++;
    }
  }
  cout << ans << '\n';
}

int main() {
  for (cin >> t; t; t--) {
    solve();
  }
  return 0;
}

B - Building an Aquarium

题目大意

\(n\) 个水箱,第 \(i\) 个水箱里面有 \(a_i\) 高的水。现在你有 \(x\) 桶水,每一桶水可以将指定的一个水箱里面的水的高度加一。问你所有水箱的最低高度最高是多少。

思路

妥妥的二分版子题,不过这里还是先讲一讲错误思路。

我们可以设计一个优先队列,堆顶就是最矮的水箱,然后遍历 \(x\) 次,每一次都将最矮的水箱里面的水的高度加一,然后操作完直接输出堆顶就行了。这种做法的时间复杂度为 \(\mathcal O(x\log_2 n)\),但是由于这题 \(x\le 10^9\),因此会直接爆掉。

我们意识到了这种做法过于依赖 \(x\) 的大小,使得了我们会超时。仔细想想,我们发现了,对于一个解 \(ans\),我们可以很轻松地在 \(\mathcal O(n)\) 的时间内判断这个解是否合法;而且我们还发现了 \(ans\) 越小,合法的概率就越高。这不就是二分吗?我们在 \([1,10^{10}]\) 内进行二分,对于一个解,如果可行就记录答案,然后尝试更大的解;否则,就往小的分。

!!! warning 注意
二分的上界不能是 \(10^9\),实际上要比 \(10^9\) 要大,极限情况下可能会达到 \(2\times 10^9\)。这里开了 \(10^{10}\),基本上不可能爆掉,但是要使用 \(64\) 位整数。

代码

#include <iostream>

using namespace std;
using ll = long long;

const ll kMaxN = 1e6 + 5;

ll a[kMaxN], t = 1, n, X, ans;

bool C(ll x) {
  ll c = 0;
  for (ll i = 1; i <= n; i++) {
    if (a[i] < x) {
      c += x - a[i];
    }
  }
  return c <= X;
}

void solve() {
  cin >> n >> X;
  for (ll i = 1; i <= n; i++) {
    cin >> a[i];
  }
  for (ll l = 1, r = 1e10; l <= r; ) {
    ll m = (l + r) >> 1;
    if (C(m)) {
      ans = m, l = m + 1;
    } else {
      r = m - 1;
    }
  }
  cout << ans << '\n';
}

int main() {
  for (cin >> t; t; t--) {
    solve();
  }
  return 0;
}

看似很♂实则很水的题目

E - Another Permutation Problem

题目大意

给定 \(n\),你需要找到一个长度为 \(n\) 的排列 \(p\),使得以下计算出来的值最大:

\[(\sum_{i=1}^{n}p_i\times i)-(\max_{j=1}^{n}p_j\times j) \]

思路

其实这题最难的部分就是找规律。我们可以设计一个测试程序,也就是 \(n\in [1,12]\) 的情况,然后使用 STL 的下一个排列函数 next_permution 枚举全排列,最后计算值就行了,并输出值最大的排列。

然后我们就可以找到规律,每一次 \(p\) 都是 \((1,2,\cdots,d,n,n-1,\cdots,d+1)\) 的形式 \((1\le d\le n)\)。因此我们尝试直接枚举 \(d\),然后用这种方法直接填充序列,最后计算值取最大就行了。

代码

#include <iostream>
#include <vector>

using namespace std;
using ll = long long;

const ll kMaxN = 25005;

ll a[kMaxN], t, n, m, ans;

int main() {
  for (cin >> t; t; t--) {
    cin >> n, ans = 0;
    for (ll k = 1; k <= n; k++) {
      for (ll i = 1; i <= n; i++) {
        if (i <= n - k) {
          a[i] = i;
        } else {
          a[n - (i - (n - k)) + 1] = i;
        }
      }
      ll mx = 0, s = 0;
      for (ll i = 1; i <= n; i++) {
        mx = max(mx, a[i] * i);
        s += a[i] * i;
      } 
      ans = max(ans, s - mx);
    }
    cout << ans << '\n';;
  }
  return 0;
}

G - Olya and Game with Arrays

题目大意

给定 \(n\),有一个二维数组,第 \(i\) 行的数字数量为 \(m_i\),也就是说这个数组是以如下形式给出的。你可以从二维数组的每一个一维数组中随便挑选一个元素(最多一次),并将这个元素移动到任意一个其它数组当中,然后以一下形式计算值。问你最大的值是多少。

示例

数组:

\[\begin{pmatrix} a_{(1,1)} & a_{(1,2)} & \cdots & a_{(1,m_1)} \\ a_{(2,1)} & a_{(2,2)} & \cdots & a_{(2,m_2)} \\ \vdots & \vdots & \ddots & a_{(3,m_3)} \\ a_{(n,1)} & a_{(n,2)} & \cdots & a_{(n,m_n)} \\ \end{pmatrix} \]

计算方法:

\[f(a)=\sum_{i=1}^{n}\min_{j=1}^{m_i}a_{(i,j)} \]

思路

这题我想了很久,但是思来想去还是贪心!我们可以这样想,对于一个数组,先计算出最小值和次小值,我们肯定是要把最小的元素抛出去才是就优的吧,那我们抛到哪里去呢?我们就枚举跑抛到了哪里,然后进行计算。

那你肯定会问:这种做法是 \(\mathcal O(n^2)\) 的,不会超时吗?当然,不过我们可以用前缀和优化。将所有的最小值抛到 \(i\),那么答案就是所有的次小值的和减去 \(i\) 的次小值,再加上所有数组的最小值最小值,因为所有的最小值都抛到 \(i\) 了。

注意

这题也需要使用 \(64\) 位整数,不然会 WA。我们著名的 tyk 巨佬就因为这个原因 WA 了好几次。(事实上,他所有 WA 的题目全部都是因为 long long 的原因)

代码

#include <iostream>
#include <algorithm>

using namespace std;
using ll = long long;

const ll kMaxN = 1e5 + 5; 

ll a[kMaxN], b[kMaxN], e[kMaxN], p[kMaxN], f[kMaxN], t, n, m, ans;

int main() {
  for (cin >> t; t; t--) {
    cin >> n, ans = 0;
    for (ll i = 1; i <= n; i++) {
      cin >> m;
      for (ll j = 1, x; j <= m; j++) {
        cin >> e[j];
      }
      sort(e + 1, e + m + 1);
      a[i] = e[1], b[i] = e[2];
      f[i] = a[i];
    }
    if (n == 1) {
      cout << a[1] << '\n';
      continue;
    }
    for (ll i = 1; i <= n; i++) {
      p[i] = p[i - 1] + b[i];
    }
    sort(f + 1, f + n + 1);
    for (ll i = 1; i <= n; i++) {  // 将最小值全部放到 i 里面
      ans = max(ans, p[n] - b[i] + f[1]);
    }
    cout << ans << '\n';
  }
  return 0;
}
posted @ 2023-12-13 17:01  haokee  阅读(15)  评论(0)    收藏  举报