比赛链接
A. Cidoai的吃饭
题目大意
给定一个正整数 n n n,再给定三个正整数 a , b , c a, \ b, \ c a, b, c。初始时 a n s = 0 ans = 0 ans=0。现在开始循环,每次循环按照从上到下的顺序选择第一条符合的执行(即执行完就再从 1. 1. 1. 开始判断):
- 若 n ⩾ a n \geqslant a n⩾a,则 n : = n − a , a n s : = a n s + 1 n := n - a, \ ans := ans + 1 n:=n−a, ans:=ans+1;
- 若不满足 1. 1. 1. 且 n ⩾ b n \geqslant b n⩾b,则 n : = n − b , a n s : = a n s + 1 n := n - b, \ ans := ans + 1 n:=n−b, ans:=ans+1;
- 若不满足 1. 2. 1. \ 2. 1. 2. 且 n ⩾ c n \geqslant c n⩾c, 则 n : = n − c , a n s : = a n s + 1 n := n - c, \ ans := ans + 1 n:=n−c, ans:=ans+1;
- 否则退出循环。
求最后 a n s ans ans 的值。
数据范围
- 1 ≤ n , a , b , c ≤ 1 0 6 1 \leq n, \ a, \ b, \ c \leq 10^6 1≤n, a, b, c≤106;
Solution
模拟即可。有一个 O ( 1 ) O(1) O(1) 的实现,就是先把 a a a 减干净,然后 b b b,最后 c c c。
时间复杂度 O ( 1 ) \mathcal{O}(1) O(1)
C++ Code
#include <bits/stdc++.h>
using i64 = long long;
using u64 = unsigned long long;
using u32 = unsigned;
using i80 = __int128_t;
using u80 = unsigned __int128_t;
using f64 = double;
using f80 = long double;
constexpr f64 pi = std::numbers::pi;
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n;
std::cin >> n;
int ans = 0;
for (int i = 0; i < 3; i++) {
int x;
std::cin >> x;
ans += n / x;
n %= x;
}
std::cout << ans << "\n";
return 0;
}
B. Cidoai的听歌
题目大意
给定一个长度为 n n n 的正整数序列 a a a。现在你需要进行若干次操作,下面展示第 i ( 1 ≤ i ≤ n ) i(1 \leq i \leq n) i(1≤i≤n) 次操作:
- 选择 k ( k ≤ n ) k(k \leq n) k(k≤n) 个不同的下标 p 1 , p 2 , ⋯ , p k p_1, \ p_2, \ \cdots, \ p_k p1, p2, ⋯, pk,然后对 ∀ j ∈ [ 1 , k ] \forall j \in [1, k] ∀j∈[1,k],令 a [ p j ] : = a [ p j ] + ( − 1 ) i − 1 a[p_j] := a[p_j] + (-1)^{i - 1} a[pj]:=a[pj]+(−1)i−1;
求至少要多少次操作能使得 a a a 的所有数全相等,同时输出这个相等的数。
数据范围
- 1 ≤ n ≤ 1 0 6 1 \leq n \leq 10^6 1≤n≤106;
- 1 ≤ a i ≤ 1 0 9 1 \leq a_i \leq 10^9 1≤ai≤109;
Solution
考虑将 a a a 的最小值和最大值分别为 l l l 和 r r r。
容易得到一个贪心的想法,就是最终相等的数一定是 ⌊ l + r + 1 2 ⌋ \lfloor \frac{l + r + 1}{2} \rfloor ⌊2l+r+1⌋,因为我们的操作是 + 1 − 1 +1-1 +1−1 交替的,且 + 1 +1 +1 先手,所以最终结果就是居中的那个数。
那么答案就很简单了,最少的操作次数一定是 r − l r - l r−l,因为这相当于 + 1 − 1 +1-1 +1−1 双向奔赴最终相等,也就是求 [ l , r ) [l, \ r) [l, r) 的区间长度。
时间复杂度 O ( n ) \mathcal{O}(n) O(n)
C++ Code
#include <bits/stdc++.h>
using i64 = long long;
using u64 = unsigned long long;
using u32 = unsigned;
using i80 = __int128_t;
using u80 = unsigned __int128_t;
using f64 = double;
using f80 = long double;
constexpr f64 pi = std::numbers::pi;
template<class T>
std::istream &operator>>(std::istream &is, std::vector<T> &a) {
for (auto &x: a) {
is >> x;
}
return is;
}
template<class T>
std::ostream &operator<<(std::ostream &os, const std::vector<T> &a) {
os << a[0];
for (int i = 1; i < a.size(); i++) {
os << " " << a[i];
}
return os;
}
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n;
std::cin >> n;
std::vector<int> a(n);
std::cin >> a;
int l = std::ranges::min(a);
int r = std::ranges::max(a);
int num = (l + r + 1) / 2;
std::cout << r - l << " " << num << "\n";
return 0;
}
C. Cidoai的植物
题目大意
题目有一个种子生成器,会给定询问需要的参数。
给定
n
,
m
,
k
n, m, k
n,m,k,分别表示矩阵的长宽和操作次数。一开始矩阵元素均为
0
0
0。
现在有两种操作:
1 j x
:表示把第 j j j 列所有为 0 0 0 的元素都变为 x x x;2 a b
:表示吧第 a a a 行第 b b b 列的元素变为 0 0 0;
求做完
k
k
k 次操作以后下面这个式子的值
⨁
i
=
1
n
⨁
j
=
1
m
p
i
,
j
×
(
(
i
−
1
)
×
m
+
j
)
\bigoplus\limits_{i = 1}^{n}\bigoplus\limits_{j = 1}^{m}p_{i, j} \times ((i - 1) \times m + j)
i=1⨁nj=1⨁mpi,j×((i−1)×m+j)
其中 p i , j p_{i, j} pi,j 表示矩阵第 i i i 行第 j j j 列的元素。
数据范围
- 1 ≤ n ≤ 2 × 1 0 4 1 \leq n \leq 2 \times 10^4 1≤n≤2×104;
- 1 ≤ m ≤ 200 1 \leq m \leq 200 1≤m≤200;
- 1 ≤ k ≤ 5 × 1 0 6 1 \leq k \leq 5 \times 10^6 1≤k≤5×106;
- 询问的参数保证均符合要求;
Solution
模拟即可。由于操作
1
1
1 的空位
0
0
0 改变需要依托于操作
2
2
2 的清空,因此用 vector
直接记录每一行为
0
0
0 的行下标即可,当变为
x
x
x 就清空这列的 vector
。
时间复杂度 O ( n m + k ) \mathcal{O}(nm + k) O(nm+k)
C++ Code
#include <bits/stdc++.h>
using i64 = long long;
using u64 = unsigned long long;
using u32 = unsigned;
using i80 = __int128_t;
using u80 = unsigned __int128_t;
using f64 = double;
using f80 = long double;
constexpr f64 pi = std::numbers::pi;
u32 seed;
u32 rnd() {
u32 res = seed;
seed ^= seed << 13;
seed ^= seed >> 17;
seed ^= seed << 5;
return res;
}
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n, m, k;
std::cin >> n >> m >> k >> seed;
const int N = n * m;
std::vector<std::vector<int>> free(m);
for (int j = 0; j < m; j++) {
free[j].resize(n);
std::iota(free[j].begin(), free[j].end(), 0);
}
std::vector g(n, std::vector<u32>(m));
for (int t = 1; t <= k; t++) {
int o = rnd() % 2;
if (o == 0) {
int j = rnd() % m;
u32 x = rnd() % N + 1;
for (int i: free[j]) {
g[i][j] = x;
}
free[j].clear();
} else {
int a = rnd() % n;
int b = rnd() % m;
free[b].push_back(a);
g[a][b] = 0;
}
}
u64 ans = 0;
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
ans ^= static_cast<u64>(i * m + j + 1) * g[i][j];
}
}
std::cout << ans << "\n";
return 0;
}
D. Cidoai的猫猫
待补
C++ Code
//
E. Cidoai的可乐
题目大意
给定 n n n 个点的两个约束 a i , d i a_i, d_i ai,di,其中 a i a_i ai 表示 i i i 的权值, d i d_i di 表示对 i i i 度的限制。
要求用这 n n n 个点构造一棵树,使得这棵树的总权值最小。
- 树的总权值是指树的每条边的权值和;
- 每条边的权值计算方式如下:
- 设边的两端是 u , v u, v u,v,那么取边权 w = min ( a u , a v ) w = \min(a_u, a_v) w=min(au,av);
- 第 i i i 个点的度不能超过 d i d_i di;
数据范围
- 1 ≤ n ≤ 1 0 5 1 \leq n \leq 10^5 1≤n≤105;
- 1 ≤ a i ≤ 1 0 9 1 \leq a_i \leq 10^9 1≤ai≤109;
- 1 ≤ d i < n 1 \leq d_i < n 1≤di<n;
- ∑ i = 1 n d i ≥ 2 ( n − 1 ) \sum\limits_{i = 1}^{n}d_i \geq 2(n - 1) i=1∑ndi≥2(n−1),即数据保证有解;
Solution
有个很容易想的贪心解法,就是考虑把 a i a_i ai 越小、 d i d_i di 越大的排在前面。
然后对于 a i a_i ai 越小的点 i i i,尽量让它连更多的边(因为树一共就 n − 1 n - 1 n−1 条边,这样其他权值更大的连边更少)。
因此最后做法就是:先(按照上述方式)排序,然后从度 d e g = n − 1 deg = n - 1 deg=n−1 开始枚举,每次消耗 min ( d e g , d i ) \min(deg, d_i) min(deg,di) 条边,最后 d e g : = d e g − min ( d e g , d i ) deg := deg - \min(deg, d_i) deg:=deg−min(deg,di)。这样一定是最优的。
时间复杂度 O ( n log n ) \mathcal{O}(n\log n) O(nlogn)
C++ Code
#include <bits/stdc++.h>
using i64 = long long;
using u64 = unsigned long long;
using u32 = unsigned;
using i80 = __int128_t;
using u80 = unsigned __int128_t;
using f64 = double;
using f80 = long double;
constexpr f64 pi = std::numbers::pi;
template<class T>
std::istream &operator>>(std::istream &is, std::vector<T> &a) {
for (auto &x: a) {
is >> x;
}
return is;
}
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n;
std::cin >> n;
std::vector<int> a(n);
std::vector<int> d(n);
std::cin >> a >> d;
std::vector<int> ord(n);
std::iota(ord.begin(), ord.end(), 0);
std::ranges::sort(ord, [&](int i, int j) {
if (a[i] != a[j]) {
return a[i] < a[j];
}
return d[i] > d[j];
});
i64 ans = 0;
int cnt = n - 1;
for (int i: ord) {
int min = std::min(cnt, d[i]);
ans += min * a[i];
cnt -= min;
}
std::cout << ans << "\n";
return 0;
}
F. Cidoai的自恋
题目大意
和 D D D 一样,这题也是用种子生成一个长度为 k k k 的序列 a a a,这表示 k k k 个询问( a i a_i ai 为第 i i i 个询问提出的数)。
现在我们需要找到一个数 x ∈ [ 1 , n ] x \in [1, n] x∈[1,n],使得它不在 a a a 中,且会导致最多的无效询问。
无效询问的定义是,在 a a a 中有一对数 ( a i , a j ) (a_i, a_j) (ai,aj) 且 i < j i < j i<j,满足以下条件之一:
- a i < x a_i < x ai<x 且 a j ≤ a i a_j \leq a_i aj≤ai;
- a i > x a_i > x ai>x 且 a j ≥ a i a_j \geq a_i aj≥ai;
我们就称第 j j j 个询问是一个无效询问。
数据范围
- 1 ≤ n , k ≤ 5 × 1 0 6 1 \leq n, k \leq 5 \times 10^6 1≤n,k≤5×106.
Solution
要使无效询问最多,则要使有效询问最少。
有效询问由两部分构成:
- 上界为 x − 1 x - 1 x−1 的最长上升子序列且满足子序列中任意两个数在原序列之间不存在比它们更大的数;
- 下界为 x + 1 x + 1 x+1 的最长下降子序列且满足子序列中任意两个数在原序列之间不存在比它们更小的数。
我们可以将所有数的第一次出现位置记录下来,称为 p o s pos pos。
为了计算上界为 x − 1 x - 1 x−1 的最长递增子序列,我们从前往后枚举 x ∈ [ 1 , n ] x \in [1, n] x∈[1,n] 。若存在两个数 x , y x, y x,y 满足 x < y x < y x<y 且 p o s [ x ] > p o s [ y ] pos[x] > pos[y] pos[x]>pos[y],则提出 x x x 这个数的询问是一个无效询问。
因此我们可以维护一个 p o s pos pos 单调递增的栈,对于当前的 x x x,若它没在 a a a 中出现,则 x x x 对应的最长递增子序列的长度即为枚举到它时栈的大小。
求最长递减子序列只要反向枚举,即枚举 n n n 到 1 1 1,栈保持维护 p o s pos pos 递增。
时间复杂度 O ( n ) \mathcal{O}(n) O(n)
C++ Code
#include <bits/stdc++.h>
using i64 = long long;
using u64 = unsigned long long;
using u32 = unsigned;
using i80 = __int128_t;
using u80 = unsigned __int128_t;
using f64 = double;
using f80 = long double;
constexpr f64 pi = std::numbers::pi;
u32 seed;
u32 rnd() {
u32 res = seed;
seed ^= seed << 13;
seed ^= seed >> 17;
seed ^= seed << 5;
return res;
}
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n, k;
std::cin >> n >> k >> seed;
std::vector<int> pos(n, -1);
for (int i = 0; i < k; i++) {
int x = rnd() % n;
if (pos[x] == -1) {
pos[x] = i;
}
}
std::vector<int> stk;
std::vector<int> pre(n);
for (int i = 0; i < n; i++) {
if (pos[i] == -1) {
pre[i] = stk.size();
continue;
}
while (!stk.empty() and pos[stk.back()] > pos[i]) {
stk.pop_back();
}
stk.push_back(i);
}
stk.clear();
std::vector<int> suf(n);
for (int i = n - 1; i >= 0; i--) {
if (pos[i] == -1) {
suf[i] = stk.size();
continue;
}
while (!stk.empty() and pos[stk.back()] > pos[i]) {
stk.pop_back();
}
stk.push_back(i);
}
int x = -1;
for (int i: std::views::iota(0, n) | std::views::filter([&](int i) { return pos[i] == -1; })) {
if (x == -1 or pre[x] + suf[x] > pre[i] + suf[i]) {
x = i;
}
}
std::cout << x + 1 << "\n";
return 0;
}