🍨🍨🍨高分篇的内容已经将机试可能涉及到的题型及考点都一一列举出来,并给大家提供了练习题目,帮助大家巩固基础加深理解。在大多数院校的机试中,学会高分篇的内容已经足够考到 90 分以上的成绩,题目不难的情况下甚至能拿到 100 分。在少数难度较大的院校也足够拿到 80 分左右的分数,如果你基础比较薄弱,建议掌握高分篇的内容就可以了。
🍨🍨🍨满分之路的内容不是常考点,只是有一定可能会考到,我们建议基础好的同学继续学习增加拿满分的把握。
🍨🍨🍨这一章我们重点来看一些较为深入的题型,包括计算几何基础、进阶背包问题、毛毛虫算法、****博弈类问题、二分答案技巧和前缀和技巧等内容。希望能帮助大家更好的掌握计算机考研机试中所涉及到的各类较难的问题。
提示:满分篇的内容选择性的学就行,不要求全部掌握,会其中一部分就很厉害了。加油!fighting!( •̀ ω •́ )✧
🧊🧊🧊2.1 计算几何基础
在考研机试中,有的时候会考到一些基础的计算几何知识,其中包括点积、叉积、凸包等内容。
首先在二维坐标下介绍一些定义:
**点:**A (x1,y1), B (x2,y2)
**向量:**向量 AB= (x2 - x1 , y2 - y1 ) = (x , y)
向量的模: |AB| = sqrt (x * x + y * y)
点积
向量的点积: 结果为 x1*x2 + y1*y2。
点积的结果是一个数值。
点积的几何意义:我们以向量 a 向向量 b 做垂线,则 | a | * cos(a,b)为 a 在向量 b 上的投影,即点积是一个向量在另一个向量上的投影乘以另一个向量,且满足交换律。
应用:可以根据集合意义求两向量的夹角,cos(a,b) = (向量 a * 向量 b) / (| a | * | b |) = x1*x2 + y1*y2 / (| a | * | b |)
叉积
向量的叉积: 结果为 x1*y2-x2*y1
叉积的结果是一个向量,是垂直于向量 a,b 所形成的平面,如果看成三维坐标的话是在 z 轴上,上面结果是它的模。
方向判定:右手定则,(右手半握,大拇指垂直向上,四指右向量 a 握向 b,大拇指的方向就是叉积的方向)
叉积的几何意义:
1、其结果是 a 和 b 为相邻边形成平行四边形的面积。
2、结果有正有负,有 sin(a,b)可知和其夹角有关,夹角大于 180°为负值。
3、叉积不满足交换律
应用:
1、通过结果的正负判断两矢量之间的顺逆时针关系
若 a x b > 0 表示 a 在 b 的顺时针方向上
若 a x b < 0 表示 a 在 b 的逆时针方向上
若 a x b == 0 表示 a 在 b 共线,但不确定方向是否相同
2、判断折线拐向,可转化为判断第三点在前两的形成直线的顺逆时针方向,然后判断拐向。
3、判断一个点在一条直线的那一侧,同样上面的方法。
4、判断点是否在线段上,可利用叉乘首先判断是否共线,然后在判断是否在其上。
5、判断两条直线是否想交(跨立实验)
根据判断点在直线那一侧我们可以判断一个线段的上的两点分别在另一个线段的两侧,当然这是不够的,因为我们画图发现这样只能够让直线想交,而不是线段,所以我们还要对另一条线段也进行相同的判断就 ok。
凸包
凸多边形:凸多边形是指所有内角大小都在[0, π]范围内的简单多边形 。
凸包:在平面上能包含所有给定点的最小凸多边形叫做凸包。
其定义为:
对于给定集合 X,所有包含 X 的凸集的交集 S 被称为 X 的凸包 。
实际上可以理解为用一个橡皮筋包含住所有给定点的形态。
凸包用最小的周长围住了给定的所有点。如果一个凹多边形围住了所有的点,它的周长一定不是最小,如下图。根据三角不等式,凸多边形在周长上一定是最优的。
凸包的求法:我们这里不详细讲解与证明凸包算法的原理,其本质就是极角坐标排序,我们主要是告诉大家如何使用凸包算法的模板。
🥥例题:DreamJudge 1113
cpp
#include <bits/stdc++.h>
#include <math.h>
using namespace std;
const int maxn=1005;
inline double sqr(double x){return x*x;}
struct point{
double x,y;
point(){}
point(double _x,double _y):x(_x),y(_y){}
//判断两点相同
bool operator ==(const point &b) const{
return x-b.x==0 && y-b.y==0;
}
point operator -(const point &b) const{
return point(x-b.x,y-b.y);
}
//叉积
int operator ^(const point &b) const{
return x*b.y-y*b.x;
}
//点积
int operator *(const point &b) const{
return x*b.x+y*b.y;
}
//重载小于号 求最左下角的点
bool operator <(const point &b)const{
return x-b.x==0?y-b.y<0:x<b.x;
}
};
struct line{
point s,e;
line(){}
line(point _s,point _e):s(_s),e(_e){}
};
double dis(point a,point b){ //求两点之间的距离
return sqrt(1.0*sqr(a.x-b.x)+1.0*sqr(a.y-b.y));
}
struct polygon{
int n;
point p[maxn];
void add(point q){p[n++]=q;}
struct cmp{
point p;
cmp(const point &p0):p(p0){}
bool operator ()(const point &aa,const point &bb){
point a=aa,b=bb;
int k=(a-p)^(b-p);
if (k==0){
return dis(a,p)-dis(b,p)<0;
}
return k>0;
}
};
//极角排序 先找到左下角的点
//重载好 point 的'<'
void norm(){
point mi=p[0];
for (int i=1;i<n;i++) mi=min(mi,p[i]);
sort(p,p+n,cmp(mi));
}
//得到凸包,点编号为 0--n-1
void Graham(polygon &convex){
norm();
int &top=convex.n;
top=0;
if (n==1){
top=1;
convex.p[0]=p[0];
return ;
}
if (n==2){
top=2;
convex.p[0]=p[0];
convex.p[1]=p[1];
if (convex.p[0]==convex.p[1]) top--;
return ;
}
convex.p[0]=p[0];
convex.p[1]=p[1];
top=2;
for (int i=2;i<n;i++){
while (top>1 && ((convex.p[top-1]-convex.p[top-2])^(p[i]-convex.p[top-2]))<=0)
top--;
convex.p[top++]=p[i];
}
if (convex.n==2 && (convex.p[0]==convex.p[1])) convex.n--;
}
double getarea(){//求凸包的面积
double sum=0;
for (int i=0;i<n;i++) sum+=(p[i]^(p[(i+1)%n]));
return sum/2.0;
}
};
polygon C;
int main(){
int t;
cin >> t;
while (t--) {
int n,x,y; cin >> n;
C.n=0;
for (int i=0;i<n;i++){
cin >> x >> y;
C.add(point(x,y));
}
polygon ans; C.Graham(ans);
double res = ans.getarea();
printf("%.1lf\n", res);
}
return 0;
}
**总结:**对于机试中的计算几何来说,记住上面的算法模板并知道是如何使用的即可。
简单的变形应用
**例:**一片森林里有很多树,每棵树对应二维平面上的一个坐标,现在小明想要在森林里放牛, 所以小明用线缠绕在树上划出牛栏的边界,已知每头牛都需要 50 单位面积的活动空间,请问小明最多可以放多少头牛?
**解析:**很明显,将树抽象成二维平面上的点,然后画一个凸多边形就是最大的面积,其实就是求凸包的面积,将凸包面积/50 即为可以放牛的数量。
🥥练习题目
DreamJudge 1160 球的半径和体积
DreamJudge 1183 Freckles
DreamJudge 1211 矩形相交
DreamJudge 1596 球形空间产生器
DreamJudge 1615 多边形的面积
DreamJudge 1620 放牧
DreamJudge 1621 玩具
🧊🧊🧊2.2 进阶背包问题
在高分篇中,我们学习了简单背包问题,和 01 背包问题。
当时我们用的二维数组来进行递推的,仔细思考就可以发现其实我们没有必要开这么大的数组。因为我们所有的递推都是从上一行推到下一行的,也就是说,我们只需要开两行的数组即可完成这个过程。我们还可以进一步压缩空间,前半段和后半段也可以进行滚动。
滚动数组(上下滚动)
cpp
#include <stdio.h>
#include <string.h>
int main() {
int dp[2][1005] = {0};//只需要 0 和 1 进行滚动
int w[1005];
int s, n;
while (scanf("%d%d", &s, &n) != EOF) {
for (int i = 1; i <= n; i++)
scanf("%d", &w[i]);
memset(dp, 0, sizeof(dp));
dp[0][0] = 1;
for (int i = 1; i <= n; i++) {
int now = i & 1;
for (int j = s; j >= 0; j--) {//将 i-1 变成 i^1
if (dp[now^1][j] == 1) dp[now][j] = 1;
if (j - w[i] >= 0 && dp[now^1][j-w[i]] == 1) dp[now][j] = 1;
}
}
if (dp[n&1][s] == 1) printf("YES\n");
else printf("NO\n");
}
return 0;
}
滚动数组(前后滚动)
cpp
#include <stdio.h>
#include <string.h>
int main() {
int dp[1005] = {0};//前后滚动
int w[1005];
int s, n;
while (scanf("%d%d", &s, &n) != EOF) {
for (int i = 1; i <= n; i++)
scanf("%d", &w[i]);
memset(dp, 0, sizeof(dp));
dp[0] = 1;
for (int i = 1; i <= n; i++) {
for (int j = s; j >= 0; j--) {//从后往前枚举
if (j - w[i] >= 0 && dp[j-w[i]] == 1) dp[j] = 1;
}
}
if (dp[s] == 1) printf("YES\n");
else printf("NO\n");
}
return 0;
}
完全背包
有 N 种物品和一个容量为 V 的背包,每种物品都有无限件可用。第 i 种物品的费用是 c[i],价值是 w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
🥥例题:DreamJudge 1569
题目解析:这个题是一个完全背包的算法模板题,直接套用完全背包的模板即可。
cpp
#include<bits/stdc++.h>
using namespace std;
int n,W,v[10005],w[10005],dp[1005];
int main(){
while(cin>>n>>W){
for(int i=0;i<n;i++)
cin>>w[i]>>v[i];
memset(dp,0,sizeof(dp));
for(int i=0;i<n;++i) //种类
for(int j=w[i];j<=W;++j) //重量从小到大枚举
dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
cout<<dp[W]<<endl;
}
return 0;
}
**特别说明:**多重背包的问题几乎没有学校机试会考,如果学有余力的同学可以尝试自己去学习一下。暴力的做法几乎人人都会,一般如果考到都需要优化。一种方法是类似二分快速幂的思想用二进制方式拆分求解,另一种方法是用单调队列来维护。
🧊🧊🧊2.3 毛毛虫算法
毛毛虫算法(官方称为尺取法)通常是指对数组保存一对下标(起点、终点),然后根据实际情况交替推进两个端点直到得出答案的方法,这种操作很像是毛毛虫(尺取虫)爬行的方式故得名。
🥥例题:DreamJudge 1570
cpp
#include<bits/stdc++.h>
using namespace std;
int a[100005];
int main() {
int T, n, s;
scanf("%d", &T);
while(T--) {
scanf("%d%d", &n, &s);
for(int i = 1;i <= n;i++)
scanf("%d", &a[i]);
int l = 1,r = 1,sum = 0,ans = 0x3f3f3f3f;
while(1) {
while(sum < s && r <= n) sum += a[r++];
if(sum < s) break;
ans = min(ans, r-l);
sum -= a[l++];
}
if(ans == 0x3f3f3f3f) printf("0\n");
else printf("%d\n", ans);
}
return 0;
}
🥥练习题目
DreamJudge 1591 逛画展
滑动窗口算法
毛毛虫算法有时候又叫做滑动窗口算法,我们可以换个方式来理解这类解题思想。
学过计算机网络的同学,都知道滑动窗口协议(Sliding Window Protocol),该协议是 TCP 协议的一种应用,用于网络数据传输时的流量控制,以避免拥塞的发生。该协议允许发送方在停止并等待确认前发送多个数据分组。由于发送方不必每发一个分组就停下来等待确认。因此该协议可以加速数据的传输,提高网络吞吐量。
滑动窗口算法其实和这个是一样的,只是用的地方场景不一样,可以根据需要调整窗口的大小,有时也可以是固定窗口大小。
滑动窗口算法是在给定特定窗口大小的数组或字符串上执行要求的操作。该技术可以将一部分问题中的嵌套循环转变为一个单循环,因此它可以减少时间复杂度。简而言之,滑动窗口算法在一个特定大小的字符串或数组上进行操作,而不在整个字符串和数组上操作,这样就降低了问题的复杂度,从而也达到降低了循环的嵌套深度。其实这里就可以看出来滑动窗口主要应用在数组 和字符串上。
可以用来解决一些查找满足一定条件的连续区间的性质(长度等)的问题。由于区间连续,
因此当区间发生变化时,可以通过旧有的计算结果对搜索空间进行剪枝,这样便减少了重复计
算,降低了时间复杂度。往往类似于" 请找到满足 xx 的最 x 的区间(子串、子数组)的 xx "
这类问题都可以使用该方法进行解决。
需要注意的是,滑动窗口算法更多的是一种思想,而非某种数据结构的使用。
实战例子
例题:尽可能使字符串相等
给你两个长度相同的字符串,s 和 t。
将 s 中的第 i 个字符变到 t 中的第 i 个字符需要 |s[i] - t[i]| 的开销(开销可能为 0),也就是两个字符的 ASCII 码值的差的绝对值。
用于变更字符串的最大预算是 maxCost。在转化字符串时,总开销应当小于等于该预算,这也意味着字符串的转化可能是不完全的。
如果你可以将 s 的子字符串转化为它在 t 中对应的子字符串,则返回可以转化的最大长度。
如果 s 中没有子字符串可以转化成 t 中对应的子字符串,则返回 0。
示例 1:
输入:s = "abcd", t = "bcdf", cost = 3
输出:3
解释:s 中的 "abc" 可以变为 "bcd"。开销为 3,所以最大长度为 3。
示例 2:
输入:s = "abcd", t = "cdef", cost = 3
输出:1
解释:s 中的任一字符要想变成 t 中对应的字符,其开销都是 2。因此,最大长度为 1。
示例 3:
输入:s = "abcd", t = "acde", cost = 0
输出:1
解释:你无法作出任何改动,所以最大长度为 1。
创作不易,点个赞吧~点赞收藏不迷路,感兴趣的宝子们欢迎关注该专栏《保研考研机试攻略》~
勤奋努力的宝子们,学习辛苦了!宝子们可以收藏起来慢慢学哦~🌷🌷🌷休息下,我们下部分再见👋( •̀ ω •́ )✧~