基础准备:
C++中的sort办法进行,要使用这个sort办法要引入< algorithm > 头文件
官方原型:
template <class RandomAccessIterator>
void sort (RandomAccessIterator first, RandomAccessIterator last);//fist为起始位置,last为末尾位置加一,也可以理解为要排序数组的长度
template <class RandomAccessIterator, class Compare>
void sort (RandomAccessIterator first, RandomAccessIterator last, Compare comp);//comp为自定义排序规则的函数
简单用法:
sort默认是从小到大
int num[105] = {99, -2, 7, 3, 5};
sort(num + 0, num + 5);
从大到小,在sort加入第三个参数
bool cmp(const int &a, const int &b) {//在排好序以后,a表示排序在前的元素,b表示排序在后的元素,加const是官方建议的,不加编译也能通过
return a > b;//这里注意一个问题就是只能写大于号或小于号,如果写等于号可能出现死排序
}
int num[105] = {99, -2, 7, 3, 5};
sort(num, num + 5, cmp);
自定义结构用法:
结构体/类
第一种用法///
struct node {
int x, y;
};
bool cmp_node(const node &a, const node &b) {
if (a.x == b.x) {return a.y > b.y;} //这行代码就是为了一个满足如果x的值相等按y的值排序的需求
return a.x > b.x;
}
node t[10];
t[0].x = 5, t[0].y = 1;
t[1].x = 7, t[1].y = 0;
t[2].x = 6, t[2].y = 9;
sort(t, t + 3, cmp_node);
第二种用法///
struct node {
int x, y;
bool operator< (const node &b) const {
return this->x > b.x;
}//重载小于号,这涉及C++语法,可选
};
node t[10];
t[0].x = 5, t[0].y = 1;
t[1].x = 7, t[1].y = 0;
t[2].x = 6, t[2].y = 9;
sort(t, t + 3);
sort办法训练题目:海贼oj网站需要借会员的私聊
代码演示:
#include<iostream>
#include<algorithm>
using namespace std;
struct student {
int num, c, m, e, sum;
};
bool cmp(const student &a, const student &b) {
if(a.sum == b.sum) {
if(a.c == b.c) {
return b.num < a.num;
}
return a.c > b.c;
}
return a.sum > b.sum;
}
student stu[305];
int n;
int main() {
cin >> n;
for(int i = 1; i <= n; i++) {
cin >> stu[i].c >> stu[i].m >> stu[i].e;
stu[i].sum = stu[i].c + stu[i].m + stu[i].e;
stu[i].num = i;
}
sort(stu + 1, stu + n + 1, cmp);
for (int i = 1; i <= 5; i++) {
cout << stu[i].num << " " << stu[i].sum << "\n";
}
return 0;
}
代码演示:
#include<iostream>
#include<algorithm>
#include<string>
using namespace std;
struct student {
string name;
int avg, cla, paper, num, sum;
char west, off;
};
student stu[105];
int n;
int func(int i) {
int t = 0;
if (stu[i].avg > 80 && stu[i].paper > 0) t += 8000;
if (stu[i].avg > 85 && stu[i].cla > 80) t += 4000;
if (stu[i].avg > 90) t += 2000;
if (stu[i].avg > 85 && stu[i].west == 'Y') t += 1000;
if (stu[i].cla > 80 && stu[i].off == 'Y') t += 850;
return t;
}
bool cmp (const student &a, const student &b) {
if (a.sum == b.sum) {
return a.num < b.num;
}
return a.sum > b.sum;
}
int main () {
int ans = 0;
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> stu[i].name >> stu[i].avg >> stu[i].cla >> stu[i].off >> stu[i].west >> stu[i].paper;
stu[i].num = i;
stu[i].sum = func(i);
ans += stu[i].sum;
}
sort(stu + 1, stu + n + 1, cmp);
cout << stu[1].name << "\n" << stu[1].sum << "\n" << ans;
return 0;
}
代码演示:
#include<iostream>
#include<algorithm>
#include<string>
using namespace std;
struct node{
int cnt;
string num;
};
int n;
node x[105];
bool cmp (const node &a, const node &b) {
if (a.num.size() == b.num.size()) {
return a.num > b.num;
}
return a.num.size() > b.num.size();
}
int main() {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> x[i].num;
x[i].cnt = i;
}
sort(x + 1, x + n + 1, cmp);
cout << x[1].cnt << "\n" <<x[1].num;
return 0;
}
二分折半查找
本质:删掉不存在的区间
作用:解决连续问题,如单调函数
核心思想:从小到大排序,以中间值arr[mid]和查找目标x比较,不断地缩小一半查找范围
操作思路:
排序(c++用sort函数)
设定一个头指针min和一个尾指针max
取中间值位置 mid = (min + max) / 2
如果中间值不是查找目标x,此时头尾指针做调整,缩小一半范围
调整:如果arr[mid] < x ,min = mid +1
如果arr[mid] > x, max = mid -1
如果arr[mid] == x,找到结果
终止条件:min > max
意义:二分折半查找实现单调函数(平方根)问题
核心代码:
while(l <= r) {
//int mid = (l + r) / 2//因为l + r可能会溢出
int mid = (r - l) / 2 + l;
if (num[mid] > x) r = mid - 1;
if (num[mid] < x) l = mid + 1;
if (num[mid] == x) return num[mid];
}
二分折半查找训练题目:海贼oj网站
代码演示:
#include<iostream>
#include<algorithm>
#include<cstdio>
using namespace std;
int n, m, numm[100005], numn[100005], f;
long long ans;
int main() {
scanf("%d%d", &m, &n);
for (int i = 0; i < m; i++) {
scanf("%d", &numm[i]);
}
for (int i = 0; i < n; i++) {
scanf("%d", &numn[i]);
}
sort(numn, numn + n);
for (int i = 0; i < m; i++) {
int l = 0, r = n - 1;
while (l <= r) {
int mid = (r - l) / 2 + l;
if (numm[i] == numn[mid]) {
if (f == 1) {
printf(" ");
}
f = 1;
printf("%d", numm[i]);
ans += numm[i];
break;
}
if (numm[i] < numn[mid]) {
r = mid - 1;
} else {
l = mid + 1;
}
}
}
printf("\n%lld\n", ans);
return 0;
}
#include<iostream>
#include<algorithm>
#include<cstdio>
using namespace std;
struct node {
int num, cnt;
};
int n, m;
node wm[100005];
bool cmp (const node &a, const node &b) {
return a.num < b.num;
}
int main () {
scanf("%d%d", &m, &n);
for (int i = 1; i <= m; i++) {
scanf("%d", &wm[i].num);
wm[i].cnt = i;
}
sort(wm + 1, wm + m + 1, cmp);
for (int i = 1; i <= n; i++) {
int t, l = 1, r = m, f = 0;
scanf("%d", &t);
while (l <= r) {
int mid = (r - l) / 2 + l;
if(wm[mid].num == t) {
printf("%d\n", wm[mid].cnt);
f = 1;
break;
}
if (wm[mid].num < t) {
l = mid + 1;
} else {
r = mid - 1;
}
}
if(f == 0) {
printf("0\n");
}
}
return 0;
}
泛型二分查找
出现题型:二分答案——函数数组
理解泛型二分前提是理解二分查找本质:删掉不存在的区间
①、在排序数组里面查找第一个大于等于给定值的元素的位置(00000000111111)
判定:寻找的目标答案的右边都满足目标条件
l = 0, r = nums.size() - 1;
while (l <= r) {
int mid = (r + l) / 2;//r + l可能会溢出,可以用l + (l + r) / 2来优化,一般数组没有2的32次方那么长,所以这样的优化一般适合非数组,比如函数
if (nums[mid] >= target) {//为什么有等号呢,因为可能在nums[mid]前还有多个nums[mid]
r = mid - 1;//为什么不是r = mid?因为如果r = mid可能导致死循环(因为循环条件用的是l <= r,在l == r时就可能出现死循环,如果想用r = mid可以用循环条件 l != r或 l < r)。那问题又来了,mid - 1最终不久会错过第一个1了吗,其实不是,如果r指向了0那么最终l(L)就会和r错开,最终l指向第一个1。所以r指向的位置永远不可能是target
} else {
l = mid + 1;//如果找不到,l指向的值就是数组里面第一个大于等于target的值
}
return l;
}
②、在排序数组里面查找最后一个小于等于给定值的元素的位置 在例如 111111111000000 中找最后一个1
判定:寻找的目标答案的左边都满足目标条件
while (l < r) {
mid = (l + r + 1) / 2;//因为除以奇数结果偏小原因,防止死循环(比如数组就只有 1 0,这时mid为0,num[mid]为1,所以l就为mid为0,导致mid一直是0)
if (num[mid] <= target) {
l = mid;
}
if (num[mid] > target) {
r = mid - 1;
}
}
泛型二分折半查找训练题目:海贼oj网站
代码演示:
#include<iostream>
#include<algorithm>
#include<cstdio>
using namespace std;
struct node {
int num, cnt;
};
int m, n;
node wm[100005];
bool cmp (const node &a, const node &b) {
return a.num < b.num;
}
int main () {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++ ) {
scanf("%d", &wm[i].num);
wm[i].cnt = i;
}
wm[0].cnt = 0;
wm[0].num = 2000000000;
sort (wm, wm + n + 1, cmp);
for (int i = 1; i <= m; i++) {
int t, l = 0, r = n;
scanf("%d", &t);
while (l != r) {
int mid = (l + r) / 2;
if (wm[mid].num >= t) {
r = mid;
} else {
l = mid + 1;
}
}
printf("%d\n", wm[l].cnt);
}
return 0;
}
二分答案
题目描述:
某林业局现在 N根原木,长度分别为 Xi,为了便于运输,需要将他们切割成长度相等的 M根小段原木(只能切割成整数长度,可以有剩余),小段原木的长度越大越好,现求小段原木的最大长度。例如,有 3根原木长度分别为 6,15,22,现在需要切成 8 段,那么最大长度为 5。
首先来枚举一下所有长度可能出现的情况:
长度l和段数的关系式
f
(
x
)
=
∑
i
=
0
N
(
X
[
i
]
/
l
)
f(x) = {\sum_{i=0}^{N}(X[i] / l)}
f(x)=i=0∑N(X[i]/l)
长度 1 2 3 4 5 6 7 8 9 ... 22
段数 43 21 14 9 8 6 5 3 3 ... 1
可以发现,每个长度都对应一个段数,也就是说按切割长度从1到最长的原木长度去枚举一定可以找到对应的段数,反过来对应的切割长度就是答案。相同理解,既然是在一堆数中找目的数,那么就可以用到二分查找。回归到二分的本质:删掉不存在的区间。那么这道题怎么去删除呢?然后就是判断是不是一种泛型二分,重要是怎么去区分是(0000011111)还是(11111100000)或者不是。所以可以借助这样一个小技巧:在例子的的答案左右取一个浮点数来分辨
长度 1 2 3 4 4.9 5 5.1 6 7 8 9 ... 22
段数 43 21 14 9 8 8 7 6 5 3 3 ... 1
所以可以判定为1111110000(如果思维能力强的人会想到这么一个想法,答案一定是和某个原木是刚好整除,那么在边角料的可控范围内小的切割长度都是可以和答案一致的,而大的切割长度就会使这个原木的切割段数变小)
代码演示:
#include<iostream>
#include<cstdio>
using namespace std;
int n, m, l = 1, r = 0;
int num[100005];
//获取各个棍子截段后的总段数
int func (int x) {
int t = 0;
for (int i = 0; i < n; i++) {
t += num[i] / x;
}
return t;
}
//根据f(x)函数数组原理,通过长度x找到对应的总段数f(x)的值,实质为线性方程,一个递减函数
int bs() {
while (l != r) {
int mid = (r + l + 1) / 2;
int t = func(mid);
if (t >= m) {
l = mid;
} else {
r = mid - 1;
}
}
return r;
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 0; i < n; i++) {
scanf("%d", &num[i]);
r = max(num[i], r);
}
printf("%d", bs());
return 0;
}
二分答案总结:二分答案就是一个函数数组问题,也就是说找到一个关系式(所以一个重点就是找到关系式),在这个关系中根据自变量x(1,2,3,4…)来寻找答案(第二个重点就是找到自变量区间),在最终答案之前的答案都是临时答案,这个临时答案只是一个移动左右指针的作用,最后都需要考虑一下是不是泛型二分。其实这也是二分查找的来源,也就是我们在高一数学中的二分解方程。只是我们习惯把之前的线性问题转换成了离散数学问题。
题目训练:
预告:二分专题练习、KMP、Sunday、Shift-[And/Or]算法