学习周报W06
前言
碎碎念
每日一题的计划正在不断优化!
虽说是每日一题,但是刷起兴致了说不定就会变成两题甚至三题呢。
要不改成每日算法题?笑)
还有就是,对于题解的撰写,也越来越清晰了,
要写重要的,写思路,写关键代码,写踩了什么坑,写能怎么优化!
第二就是,终于是磨完了论文,也算是体会了一下学术牛马的处境了。悲)
花了总计三天?
最长24小时没睡硬给搞起来了 O。o
计划
当然,每日一题模块也得更新啦,不能叫每日一题了,叫每日算法吧。
还有就是,要重新拾起网课了,已经两周没看了。
每日一题
P1116 车厢重组
题目描述
在一个旧式的火车站旁边有一座桥,其桥面可以绕河中心的桥墩水平旋转。一个车站的职工发现桥的长度最多能容纳两节车厢,如果将桥旋转 180 度,则可以把相邻两节车厢的位置交换,用这种方法可以重新排列车厢的顺序。于是他就负责用这座桥将进站的车厢按车厢号从小到大排列。他退休后,火车站决定将这一工作自动化,其中一项重要的工作是编一个程序,输入初始的车厢顺序,计算最少用多少步就能将车厢排序。
思路
想一想情况:
最优情况:类似 $a[1, 3, 2]$ 的序列,只需要一次 $swap$ 操作即可。
最坏情况:类似 $a[3,2,1]$ 的序列,需要三次 $swap$ 操作:
1. $a[3,2,1]$ -> $a[2,3,1]$
2. $a[2,3,1]$ -> $a[2,1,3]$
3. $a[2,1,3]$ -> $a[1,2,3]$
看了一眼
似乎就是冒泡?
先直接模拟冒泡行为看看吧? 数据量不大 $N(≤10000)$
提交 #1 <100pt>
哈哈,我是↑↓。
tmd 我本地的编译器默认行为是设置 0 搞得我调试半天没看出哪里问题。
关键代码
1 | void solve() { |
关键点
逆序对与交换次数的关系
结论
在只能交换相邻元素的情况下,
最少交换次数 = 逆序对数。
为什么逆序对数量等于最少交换次数?
思考这样一个场景:
- 每次只能交换相邻的两节车厢。
- 要把所有车厢排成升序。
那么一个较大的车厢,如果它前面有比它小的车厢,就需要一路往后挪。 每交换一次,它就和它后面的小车厢的逆序对减少一个。
一次交换,只能消除一个逆序对。
最终目标是让所有逆序对都消除掉,数组变成升序,所以:总交换次数 = 逆序对总数。
所以:在只能交换相邻元素的情况下,每次交换消除1个逆序对,最少交换次数就是逆序对数量。
优化 #1
用冒泡排序时间复杂度是 $O(N^2)$,对于 $N \le 10000$,在极限数据下会超时。
用归并排序可以在 $O(N \log N)$ 时间里统计逆序对数量。
1 | const int MAXN = 1e4 + 5; |
后记
用一句通俗易懂的话来说:实际上就是统计要发生多少次交换行为。
用正式一点的话来说:
在本题这种只能交换相邻元素的条件下,
逆序对数量恰好就是最终需要发生的总交换次数。
换句话说,就是统计:
要把所有元素移动到正确位置,总共需要多少次相邻交换操作。
P2676 [USACO07DEC] Bookshelf B
题目描述
Farmer John 最近为奶牛们的图书馆添置了一个巨大的书架,尽管它是如此的大,但它还是几乎瞬间就被各种各样的书塞满了。现在,只有书架的顶上还留有一点空间。
所有 $N(1≤N≤20,000)$ 头奶牛都有一个确定的身高 $H_i(1≤H_i≤10,000)$。设所有奶牛身高的和为 $S$。书架的高度为 $B$,并且保证 $1≤B≤S<2,000,000,007$。
为了够到比最高的那头奶牛还要高的书架顶,奶牛们不得不像演杂技一般,一头站在另一头的背上,叠成一座“奶牛塔”。当然,这个塔的高度,就是塔中所有奶牛的身高之和。为了往书架顶上放东西,所有奶牛的身高和必须不小于书架的高度。
显然,塔中的奶牛数目越多,整座塔就越不稳定,于是奶牛们希望在能够到书架顶的前提下,让塔中奶牛的数目尽量少。现在,奶牛们找到了你,希望你帮她们计算这个最小的数目。
思路
贪心算法 + 排序
Commit 1:AC
核心代码
1 | void solve() { |
优化
没什么可以优化的地方了
不过可以这样写:
1 | sort(vec.begin(), vec.end(), greater<int>()); |
或
1 | sort(vec.rbegin(), vec.rend()); |
P1781 宇宙总统
题目描述
地球历公元 6036 年,全宇宙准备竞选一个最贤能的人当总统,共有 n 个非凡拔尖的人竞选总统,现在票数已经统计完毕,请你算出谁能够当上总统。
票数可能会很大,可能会到 100 位数字,且 $1≤n≤20$ 。
思路
基本思路:高精度 + 排序
高精度 -> 大数string
排序 -> 基排?手写比较器?
所以思路很清晰了 手写string比较器
1. 60pt
只过了 60% 的点
原来是没注意到 自己的 逻辑错误了,从末尾往前比了。
2. AC
关键代码
1 | bool cmp(pair<string, int> &pa, pair<string, int> &pb) { |
关键点
- 大数string 高位在开头,别老是粗心搞错
- cpp中不需要循环比较,本身对于string就重载了比较符号,基于字典序比较(lexicographical compare)
优化
优化一:lambda
1 | sort(v.begin(), v.end(), [](pair<string, int> &a, pair<string, int> &b) { |
优化二:pair支持重载符号
std::pair
在标准库里已经给你实现好了所有的比较运算符(==, !=, <, <=, >, >=
),它们的行为都是先比较 .first
,如果 .first
相等再比较 .second
所以:
1 | if (a.first != b.first) return a.first > b.first; |
等价于
1 | return a < b; |
P1059 [NOIP 2006 普及组] 明明的随机数
题目描述
明明想在学校中请一些同学一起做一项问卷调查,为了实验的客观性,他先用计算机生成了 N 个 1 到 1000 之间的随机整数 (N≤100),对于其中重复的数字,只保留一个,把其余相同的数去掉,不同的数对应着不同的学生的学号。然后再把这些数从小到大排序,按照排好的顺序去找同学做调查。请你协助明明完成“去重”与“排序”的工作。
思路
value(N) 在 $[1, 1000]$ 之间 直接 选择鸡排。
P1093 [NOIP 2007 普及组] 奖学金
题目描述
某小学最近得到了一笔赞助,打算拿出其中一部分为学习成绩优秀的前 5 名学生发奖学金。期末,每个学生都有 3 门课的成绩:语文、数学、英语。先按总分从高到低排序,如果两个同学总分相同,再按语文成绩从高到低排序,如果两个同学总分和语文成绩都相同,那么规定学号小的同学排在前面,这样,每个学生的排序是唯一确定的。
任务:先根据输入的 3 门课的成绩计算总分,然后按上述规则排序,最后按排名顺序输出前五名名学生的学号和总分。
注意,在前 5 名同学中,每个人的奖学金都不相同,因此,你必须严格按上述规则排序。例如,在某个正确答案中,如果前两行的输出数据(每行输出两个数:学号、总分) 是:
1 | 7 279 |
这两行数据的含义是:总分最高的两个同学的学号依次是 7 号、5 号。这两名同学的总分都是 279 (总分等于输入的语文、数学、英语三科成绩之和) ,但学号为 7 的学生语文成绩更高一些。
如果你的前两名的输出数据是:
1 | 5 279 |
则按输出错误处理,不能得分。
思路
定义一个学生结构体,然后手写一个比较器,
直接用sort排序后输出前五位,
要注意学生数可能不足五位。
核心代码
1 | bool cmp(const stu& s1, const stu& s2) { |
补充
当然,也可以直接用lambda函数。
P1923 【深基9.例4】求第 k 小的数
思路
题目要求$O(n)$,所以需要利用分治思想
关键代码
1 | void qsort(int l,int r) { |
亮点
- qsort中,采用了 do while 的结构,使得可以复用 j 不用写额外逻辑
- 对于快排而言 在一次分区之后 会出现 三个区域 $range(num < mid)$、$range(num = mid)$ 和 $range(num >mid)$ 我们只搜索 k 可能存在的区域
错误
- 忘记优化IO了,第一次被卡IO卧槽
P1177 【模板】排序
题目描述
将读入的 $N$ 个数从小到大排序后输出。
输入格式
第一行为一个正整数 $N$。
第二行包含 $N$ 个空格隔开的正整数 $a_i$,为你需要进行排序的数。
输出格式
将给定的 $N$ 个数从小到大输出,数之间空格隔开,行末换行且无空格。
输入输出样例 #1
输入 #1
1 | 5 |
输出 #1
1 | 1 2 4 4 5 |
说明/提示
对于 $20%$ 的数据,有 $1 \leq N \leq 10^3$;
对于 $100%$ 的数据,有 $1 \leq N \leq 10^5$,$1 \le a_i \le 10^9$。
思路
排序模板题,纯练习排序模板了,
练练三大排序吧。
确实是生疏了,写了好一会,不过也学到了新的调试方法:
1 |
|
P1271 【深基9.例1】选举学生会
题目描述
学校正在选举学生会成员,有 $n$($n\le 999$)名候选人,每名候选人编号分别从 $1$ 到 $n$,现在收集到了 $m$($m \le 2000000$)张选票,每张选票都写了一个候选人编号。现在想把这些堆积如山的选票按照投票数字从小到大排序。
输入格式
输入 $n$ 和 $m$ 以及 $m$ 个选票上的数字。
输出格式
求出排序后的选票编号。
思路
$1 <= n <= 999$,$1 <= m <= 2*10^6$ 一眼鸡排。
遂使用计数排序。
没什么好说的,一遍过。
1 | int n, m; |