A题
一、题目做法分析
题目让我们求一个序列中以从第 K K K个数开始的每个数为结尾的区间内第k大的数,当时想的是用vector来做,每个数用insert来插入到lower_bound这个数的位置,这样就可以保证序列的单调不减,求最大时序列中倒数第 K K K个数就是当前的答案
于是,就有了考试时写的代码:
#include <bits/stdc++.h>
using namespace std;
vector<int> vec;
vector<int> a;
int n, k;
int main() {
freopen("kmax.in", "r", stdin);
freopen("kmax.out", "w", stdout);
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
a.push_back(0);
cin >> n >> k;
for (int i = 1; i <= n; i++) {
a.push_back(0);
cin >> a[i];
}
int i = 1;
for (i = 1; i <= k - 1; i++) {
vec.insert(lower_bound(vec.begin(), vec.end(), a[i]), a[i]);
}
//vec单调不减
for (; i <= n; i++) {
vec.insert(lower_bound(vec.begin(), vec.end(), a[i]), a[i]);
cout << *(vec.end() - k) << '\n';
}
return 0;
}
但后来考试结束时测出来结果是TLE,后来发现vector的insert函数的时间复杂度是
O
(
N
)
O(N)
O(N)
代码里看似是
O
(
N
l
o
g
N
)
O(NlogN)
O(NlogN)的时间复杂度实际上是
O
(
N
2
l
o
g
N
)
O(N^2logN)
O(N2logN) ,而n的规模又是5 * 105,最终导致了TLE
本题的正解其实是用堆,可以用一个小顶堆维护序列中的数,如果堆中的元素个数大于了 K K K,那就一直pop堆顶元素,直到堆中元素个数等于 K K K,堆顶就是答案
代码如下:
#include <bits/stdc++.h>
using namespace std;
int n, k;
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin >> n >> k;
vector<int> a(n + 1);
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
priority_queue<int, vector<int>, greater<int>> pq;
for (int i = 1; i <= k - 1; i++) {
pq.push(a[i]);
}
for (int i = k; i <= n; i++) {
pq.push(a[i]);
while (pq.size() > k) {
pq.pop();
}
cout << pq.top() << '\n';
}
return 0;
}
二、收获与总结
1、存在的一些问题
首先,对于一些STL容器只了解大概的用法,并不精通各种函数本身的时间复杂度,最终导致写vector来做题超时
其次,对于一些基础的数据结构不够熟悉,比如说堆这种结构,对它在实际中具体应用还不够熟悉,导致有的时候遇到了堆的题,却反应不出来要用堆去做这道题。
2、自己想的一些解决方法
第一,在学习或使用一些STL容器的时候,应该将这个容器深刻的理解,而不是只知道表面的用法,这样在考试的时候才能准确判断时间和空间允不允许使用这个容器
第二,对于一些数据结构,可以在学习或复习时适当做一些这个数据结构的经典的用途的题,并整理下来这种结构的各种用途,让它成为帮助我实现自己思路的一种工具
B题
一、题目做法分析
这道题考试的时候没想出正解,写了一个很长的dp,这里就不放代码了
这道题的解是贪心,因为从题目描述可以得知,每个标有2的数字都可以往两边扩散,所以小蓝涂色的时候尽量要先从标有2的数开始涂,并且,涂完之后,包含这个2的这一整个内部没有0的块和块周围的两个0都可以涂上红色,如图所示:
以序列0 1 1 2 2 2 1 0 0 1 0为例(下标从1开始)
将中间的任意一个2涂成红色后,区间
[
1
,
8
]
[1, 8]
[1,8] 都会被涂成红色
还剩区间
[
9
,
11
]
[9, 11]
[9,11] 没有涂成红色,而这个时候,先涂1的话,就可以让包含这个1的这一整个内部没有0的区间和区间任选一端的一个0涂成红色
至于选区间左边的0还是右边的0,就看当前
i
i
i的位置是否为0,若等于0,则选左边的0,否则选右边的0
可以用两个指针,一个 i i i, 一个 j j j。 i i i指向当前枚举区间的第一个数, j j j指向区间后的第一个0(区间内的数都是非0的)
代码如下:
#include <vector>
#include <iostream>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t;
cin >> t;
while (t-- > 0) {
int ans = 0;
int n;
cin >> n;
vector<int> a(n);
for (int i = 0; i < n; i++) {
cin >> a[i];
}
int i = 0;
int j = 0;
while (i < n && j < n) {
bool flag = false;
while (a[j + 1] != 0) {
j++;
if (a[j] == 2) {
flag = true;
}
}
if (flag) {
ans++;
i = j + 2;
} else {
ans++;
if (a[i] == 0) {
i = j + 1;
} else {
i = j + 2;
}
}
j = i;
}
cout << ans << '\n';
}
return 0;
}
二:收获与总结
存在的一些问题
其实考试的时候我是想过从较大的数开始涂的,但后来还是写了一个错误的解。我觉得我准确分析每一道题思路的能力还有待提升,很多时候不太能判断哪个是对,哪个是错,导致做不对题
自己想的一些解决方法
在空闲时间多练一些题,找到做题的感觉
C题
一:题目做法分析
这道题其实就是一个贪心求最大不重叠子区间的问题,但要做一些转化
因为题目要求的是两个区间重叠,并且各种重叠的部分不相交,可以将每两个重叠的区间视为一个新的区间,用一个vector把新的区间存起来,再求出新的区间数组里的最大不重叠子区间问题的答案,设这个答案为 M M M,则在原始的数组里用了 2 ∗ M 2*M 2∗M个原始的区间,因为每个新区间都是由两个原始的区间组合而成的,最后用总区间数减去 2 ∗ M 2*M 2∗M就是问题的答案
代码如下:
#include <bits/stdc++.h>
using namespace std;
struct Range {
int l;
int r;
};
vector<Range> vec;
int t;
int n;
vector<Range> a;
bool cmp(const Range& r1, const Range& r2) {
return r1.r < r2.r;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin >> t;
while (t-- > 0) {
vec.clear();
a.clear();
cin >> n;
vec.push_back({ 0, 0 });
for (int i = 1; i <= n; i++) {
vec.push_back({ 0, 0 });
cin >> vec[i].l >> vec[i].r;
}
//处理出新的区间数组
for (int i = 1; i <= n; i++) {
for (int j = i + 1; j <= n; j++) {
if (vec[i].r >= vec[j].l && vec[i].l <= vec[j].r) {
a.push_back({ min(vec[i].l, vec[j].l), max(vec[j].r, vec[i].r) });
}
}
}
sort(a.begin(), a.end(), cmp);
if (a.empty()) {
cout << n << '\n';
continue;
}
//贪心法求最大不重叠子区间
int now = a[0].r;
int ans = 1;
for (int i = 1; i < a.size(); i++) {
if (a[i].l <= now) {
continue;
} else {
ans++;
now = a[i].r;
}
}
cout << n - (2 * ans) << '\n';
}
}
二:收获与总结
存在的一些问题
从这道题里面,我感觉到了两个问题,首先,是思维有的时候太不灵活了,不知道怎么变通,比如在做这道题的时候,想不到可以把两个小区间合成一个大区间。其次,我觉得我现在对一些非常基础的算法不够熟悉,比如贪心法解决区间的问题,如果我对这些很熟悉,可能也能帮助我想到合成大区间的办法
自己想的一些解决方法
对于一些基础的算法,我应该要有规律的复习,隔一段时间就做几道这个知识点的题,增强记忆
D题
一:题目做法分析
这道题的 N N N很大, O ( n 2 ) O(n^2) O(n2)的复杂度肯定是过不了的, O ( n ) O(n) O(n)的做法也不太现实,多半是用某个复杂度为 O ( n l o g n ) O(nlogn) O(nlogn)的算法解决。考虑一下二分,而二分要有单调性,这道题的单调性就是是否满足要求
以第二组样例举例
5
1 4 5 9 10
集合
b
b
b
=
=
= {
1
,
4
,
5
,
9
,
10
{1, 4, 5, 9, 10}
1,4,5,9,10 }
令集合
a
a
a为集合
b
b
b的补集,即
a
a
a
=
=
= {
2
,
3
,
6
,
7
,
8
{2, 3, 6, 7, 8}
2,3,6,7,8 }
当想要找最小的
x
x
x时,即找
x
x
x的范围的左端点,二分则不用向上取整,二分的check函数只需要检查从
x
x
x到
n
n
n中是否每个数都有一个符合要求的和它配对的数,即检查取最大的那些对是否有数来配对,所以大的数尽量要配对一个小的,这样剩下的数就可以和大的配对取最下
想找最大的 x x x时,思路也是一样的,不过二分时要向上取整,check函数是检查 1 1 1到 x x x中是否每个数都比对应的数小即可
代码如下:
#include <iostream>
#include <vector>
using namespace std;
const int N = 5e6 + 5;
int n;
vector<int> a = { 0 };
vector<int> b = { 0 };
int L, R;
bool check1(int x) {
for (int i = 1; i <= x; i++) {
if (a[i] > b[n - x + i]) {
return false;
}
}
return true;
}
bool check2(int x) {
for (int i = x + 1; i <= n; i++) {
if (a[i] < b[i - x]) {
return false;
}
}
return true;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin >> n;
for (int i = 1; i <= n; i++) {
a.push_back(0);
cin >> a[i];
}
for (int i = 1; i <= 2 * n; i++) {
if (*lower_bound(a.begin(), a.end(), i) != i) {
b.push_back(i);
}
}
int left = 0, right = n, middle;
//找左端点
while (left < right) {
middle = (left + right) >> 1;
if (check2(middle)) {
right = middle;
} else {
left = middle + 1;
}
}
L = left;
left = 0, right = n;
//找右端点
while (left < right) {
middle = (left + right + 1) >> 1;
if (check1(middle)) {
left = middle;
} else {
right = middle - 1;
}
}
R = left;
cout << (R - L + 1) << '\n';
return 0;
}
二:收获与总结
存在的一些问题
考试的时候其实也想到了二分,只不过没写对,最后乱写了一个交上去,还是做错了。二分的难点在check函数,我当时也没怎么想,用了一个特别复杂的方法来check,不仅没写对,还耗费了大量时间
这也反映出了我做题的一个问题,这个问题也已经存在很久了,我一直还没改掉。就是说我每次考试总是题目看到一遍就开写,然后写着写着就突然发现不对,然后想重新想思路,耗费了我非常多时间,并且对心态影响很大
自己想的一些解决方法
其实要解决这个问题不是很难,只要在考试的时候慢下来就好了。比如我在补这套题的时候,前天老师讲完了第四题,我开始补题,当时在课上也只听懂了个大概,然后就开写,最后写了很久都没弄对,只好改天再弄。而昨天晚自习,我去机房继续补题,但当时我拿了草稿本,先花了20分钟把这道题的思路捋得清楚后,再写了一遍代码,然后交上去就过了
最后总结
我觉得在之后的学习中,我可以在以下几方面多努努力:
第一:先把基础算法学好,复习好,确保基础一定要稳固
第二:可以多练一些提升思维的题,例如dp、贪心等等
第三:考试的时候心态一定要好,不能慌,每到题都想好了再写
注:第一次写总结,谢谢观看