CSP202109
00 一些补充知识
-
常用stl内容
- sort函数的使用是必须掌握的。
- vector, map, set的使用。
-
常用算法与思想
-
二分
二分的核心思想是:对于某个性质 C C C,以及有限良序集 S S S, ( ∃ s ∈ S ) ( ∀ s ′ ≤ s ) ( ∀ s ′ ′ > s ) ( C ( s ′ ) ≠ C ( s ′ ′ ) ) (\exist s\in S)(\forall s'\le s)(\forall s'' >s)(C(s')\neq C(s'')) (∃s∈S)(∀s′≤s)(∀s′′>s)(C(s′)=C(s′′))。
直观来说,在一个良序集,存在一个分界线,两侧的元素有某个不同的性质。
- 二分查找:可以使用函数 l o w e r _ b o u n d ( b e g i n , e n d , x ) lower\_bound(begin, end, x) lower_bound(begin,end,x)找到范围内第一个不小于x的元素,或使用 u p p e r _ b o u n d ( b e g i n , e n d , x ) upper\_bound(begin, end, x) upper_bound(begin,end,x)找到第一个大于x的元素
- 对答案二分:通常有最大化最小值、最大化平均值、第k大值等
-
前缀和/差分
- 二者互为逆运算。用来求一段区间 [ l e f t , r i g h t ) [left,right) [left,right)的和。
-
贪心
- 一种思想:可以看看Dijkstra算法和决策树。并不一定可以得到最优解。
- 贪心通常用来提出一种先取最大/小值猜想,之后要证明猜想能导出最优解,通常使用数学归纳法。
-
动态规划
- 通常有背包问题等,通过之前状态与状态转移方程,推出当前状态。
- 个人认为和第二数学归纳法很像。
-
复杂度分析
- 时间复杂度分析:排序为nlogn,循环为循环长度。嵌套代码时间复杂度要相乘,否则取多块的最大值。
如果代入n后,时间复杂度大于1e9,通常说明复杂度过高。
- 空间复杂度分析:比较少,通常数组大小不要超过 4 × 1 0 6 4\times 10^6 4×106即可。
-
-
简单的文件读写
- 使用以下代码,可以改变io文件,但是记得提交时将其注释掉或删除
int main() { //将in.txt和out.txt改为你的输入/输出文件 freopen("in.txt", "r", stdin); freopen("out.txt", "w", stdout); }
- 使用以下代码,可以加快cin读取速度,通常CSP认证的题目中不需要使用
int main() { cin.tie(0); ios::sync_with_stdio(false); }
01 数组推导
题目
标签
看不出来
输入输出
数据范围
思路
显然,通过数组 A A A可以求得数组 B B B,但反过来只能求出数组 A A A每个元素的范围。
注意到:对于集合
M
,
N
M,~N
M, N以及运算
+
+
+(∪),max运算具有吸收律。对于
B
i
B_i
Bi,有
B
i
=
m
a
x
(
A
1
,
…
,
A
i
)
=
m
a
x
(
m
a
x
(
A
1
,
…
,
A
i
−
1
)
,
m
a
x
(
A
i
)
)
=
m
a
x
(
B
i
−
1
,
A
i
)
\begin{aligned} B_i&=max(A_1,\dots,A_i)\\ &=max(max(A_1,\dots,A_{i-1}), max(A_i))\\ &=max(B_{i-1},A_i) \end{aligned}
Bi=max(A1,…,Ai)=max(max(A1,…,Ai−1),max(Ai))=max(Bi−1,Ai)
当
B
i
−
1
=
B
i
B_{i-1}=B_i
Bi−1=Bi时,
0
≤
A
i
≤
B
i
−
1
0\leq A_i\leq B_{i-1}
0≤Ai≤Bi−1;当
B
i
−
1
<
B
i
B_{i-1}<B_i
Bi−1<Bi时,
B
i
=
A
i
B_i=A_i
Bi=Ai,就可以求得
A
A
A中每个元素的可能范围
注意
- A中元素都是自然数
代码
#include <bits/stdc++.h>
using namespace std;
int main() {
int n;
cin >> n;
vector<int> B(n+1);
int maxima = 0, minima = 0;
for (int i = 1; i <= n; ++i) {
cin >> B[i];
maxima += B[i];
minima += (B[i] > B[i-1]) ? B[i] : 0;
}
cout << maxima << endl << minima;
return 0;
}
02 非零段划分
题目
标签
很有意思的思维题?
输入输出
数据范围
思路
-
因为要求非零段的个数,那在数组前后各添加一个0,不会影响结果。
-
因为每次将小于p的数字变为0,所以连续的相同数字可以看作一个数字。
-
进行了上面两个变换,现在数组的情况如下( A i A_i Ai代表非零段):
0 A 1 0 A 2 … A m 0 0~A_1~0~A_2~\dots A_m~0 0 A1 0 A2 …Am 0
考察非零段 A A A,由于其中不存在连续的相同元素,且前、后一定是零段。令 A = { 0 } + A + { 0 } A=\{0\}+A+\{0\} A={0}+A+{0}, A A A中的每个元素 a i a_i ai一定满足下面几种情况之一:- a i > a i − 1 且 a i > a i + 1 a_i>a_{i-1}且a_i>a_{i+1} ai>ai−1且ai>ai+1。这种情况下, a i a_i ai变为0时,其两侧元素已为0,非零段数量减少
- a i < a i − 1 且 a i < a i + 1 a_i<a_{i-1}且a_i<a_{i+1} ai<ai−1且ai<ai+1。这种情况下, a i a_i ai变为0时,两侧元素均不为0,非零段数量增加
- otherwise, a i a_i ai变为0时,非零段数量不变。
-
由于每个数字不大于 1 0 4 10^4 104,可以记录下每个数值的数字变为0时,对非零段数量的影响。对于数值 m m m,其影响 d i f [ m ] dif[m] dif[m]由上面三种情况确定,权重分别为 − 1 , + 1 , 0 -1,+1,0 −1,+1,0。将小于 p p p的数变为0,其改变的非零段数量为 ∑ k = 1 p − 1 d i f [ k ] \sum_{k=1}^{p-1}dif[k] ∑k=1p−1dif[k]。选取所有 p p p中增加数量最大的,加上初始非零段数量 s s s即可。
注意
- 注意看题?
- 使用std::unique()函数应该也可以去重,我没有试过。此函数通常在离散化问题中使用,有兴趣可以看看。
代码
#include <bits/stdc++.h>
using namespace std;
int n, a;
const int N = 10007;
int main() {
cin.tie(0);
ios::sync_with_stdio(false);
cin >> n;
vector<int> A(1), dif(N);
for (int i = 1; i <= n; ++i) {
cin >> a;
if (a == A[A.size()-1]) continue;
A.push_back(a);
}
if (A[A.size()-1]) A.push_back(0);
for (int i = 1; i < A.size() - 1; ++i) {
if (A[i] < A[i-1] && A[i] < A[i+1]) {
dif[A[i]]++;
} else {
if (A[i] > A[i-1] && A[i] > A[i+1]) {
dif[A[i]]--;
}
}
}
int ans = 0, s = -1;
for (int i = 0; i < A.size(); ++i) {
s += (A[i] == 0);
}
for (int i = 1; i < N; ++i) {
s += dif[i];
ans = max(ans, s);
}
cout << ans;
return 0;
}