一次日常考试后的总结

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 2M个原始的区间,因为每个新区间都是由两个原始的区间组合而成的,最后用总区间数减去 2 ∗ M 2*M 2M就是问题的答案

代码如下:

#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、贪心等等
第三:考试的时候心态一定要好,不能慌,每到题都想好了再写

注:第一次写总结,谢谢观看

  • 16
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值