一、递归
核心思想:将大问题分解成多个小问题,特别注意递归的边界结束条件
1.跳台阶821. 跳台阶 - AcWing题库
斐波那契数列:这一级的台阶数和为上一级的台阶数加上 上上级的台阶数
cpp
#include<bits/stdc++.h>
using namespace std;
int func(int i){
if(i==1)
return 1;
else if(i==2)
return 2;
else
return func(i-1)+func(i-2);
//
}
int main(){
int n;
cin>>n;
cout<<func(n);
return 0;
}
二、贪心算法
本质是:由局部最优推导到全局最优----->每个阶段都取最优结果
主要题目

题目1.合并果子P1090 [NOIP 2004 提高组] 合并果子 - 洛谷
利用小根堆 哈夫曼树
1.构建小根堆 ,引入头文件 #include<queue> 定义小根堆 priority<int,vector<int>,greater<int>>q
定义小根堆 的: 类型, vector(底层容器),greater<int> greater为比较器
思想:
1,每次都取两个最小的top元素, 相加 res 进行累加
2.把相加的结果继续push到栈中
cpp
#include<iostream>
#include<queue>
#include<algorithm>
#include<vector>
#include <functional>
using namespace std;
int n;
//关键 构建小根堆 //priority_queue默认为大根堆 头文件为:#include<queue>
//构建小根堆定义: 在内置为 类型, 底层容器, greater<int>
priority_queue<int,vector<int>,greater<int>> q;
int main(){
cin>>n;
for(int i =0;i<n;i++) {
int a;
cin>>a;
q.push(a);
}
int res = 0;
while(q.size()>1){
int a = q.top();
q.pop();
int b = q.top();
q.pop();
res +=a+b;//结果进行累加
q.push(a+b);//把每次相加的结果压入堆中
}
printf("%d\n",res);
return 0;
}
二、区间问题:
1.区间选点
区间最大重叠数


贪心思想:输入区间 → 按右端点升序排序 → 遍历选不重叠区间 → 统计数量
ed 表示上一个节点的右端点 ,
将当前节点的左端点与上一个节点的右端点进行比较,如果l>ed ,则无重叠,res++
思路:
1.将区间右端点进行排序,从小到大
2.依次遍历,将当前节点的左端点与上一个节点的右端点进行比较,如果l>ed ,则无重叠,res++,更新ed 为当前节点的右端点
适用场景:解决 "最大不重叠区间数""最少点覆盖所有区间" 等经典贪心问题,时间复杂度主要由排序决定(O(nlogn)),效率很高。
cpp
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
const int N =100010;
int n;
struct point{
int l,r;
bool operator< (const point &w)const{
return r<w.r; //按右端点r升序
} //重载小于号 //为什么右端点
};
vector<point> range;
int main(){
scanf("%d",&n);
for(int i = 0;i<n;i++){
int l,r;
scanf("%d%d",&l,&r);
range.push_back({l,r});
}
sort(range.begin(),range.end());
int res = 0, ed= -2e9;//res 不重叠的区间数,ed上一个选中区间的右端点数,初始值为负无穷
for(int i = 0;i<n;i++){
if(range[i].l>ed){//如果当前区间的左端点数>上一个区间右端点数, 则没有区间重叠
res++;
ed=range[i].r; //更新为其区间右端点
}
}
printf("%d\n",res);
return 0;
}
重载运算符
// 重载 <,让 sort 知道按右端点升序比
1.结构体内部 重载
cpp
// 重载小于号 <,用于sort排序
bool operator<(const point &w) const {
return r < w.r; // 按右端点 r 升序排列
// 如果想按左端点升序,就写 return l < w.l;
// 如果想降序,就写 return r > w.r;
参数说明
const point &w
const:保证不会修改传入的 point 对象,符合常量语义 &引用传递,避免拷贝对象,提升效率
末尾的 const 这是常量成员函数的标记,保证这个函数不会修改当前对象,必须写
题目2.最大不相交区间数量
背景tbei'jbeibe112. 雷达设备 - AcWing题库

题解与上一题一样:
题目3:P1803 凌乱的yyy / 线段覆盖 - 洛谷
与上题思路一致
cpp
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
struct node{
int l,r;
bool operator<(const node &w)const{
return r<w.r;
}
};
vector<node> e;
int n,a,b;
int main(){
scanf("%d",&n);
for(int i = 0;i<n;i++){
scanf("%d%d",&a,&b);
e.push_back({a,b});
}
sort(e.begin(),e.end());
int res = 0, last_time = -1;
for(int i = 0;i<n;i++){
if(e[i].l>=last_time) {
res++;
last_time = e[i].r;
}
}
printf("%d",res);
return 0;
}
题目3 区间分组(会议室调度问题)
题型:会议室调度
核心要求:要用最少的分组容纳最多的区间,区间之间不重叠
贪心策略:
1.将区间按左端点依次排序(模拟会议开始的时间)
2.资源复用:用小根堆进行维护每个区间的右端点,堆顶元素为:最早结束会议的时间
步骤:
1.按照左端点进行排序
2.遍历处理: 如果小根堆不为空,并且 当前节点的左端点的值大于小根堆的值, 把堆顶弹出队若当前区间的左端点 > 堆顶(最早空闲组的结束时间)→ 复用该组,弹出堆顶并将当前区间右端点入堆
若当前区间的左端点 ≤ 堆顶 → 无法复用,新开一组,将当前区间右端点入堆;(根中节点数增加)---也就是其分组增加
3.输出 堆的大小 即为分组的最小值
背景例题:
cpp
#include<iostream>
#include<algorithm>
#include<vector>
#include<queue>
using namespace std;
int n ;
struct point{
int l,r;
bool operator<(const point&w)const{
return l<w.l;
}
};
//按左端点排序,小根堆记各组结束时间,能复用就更更新,不能就新开,堆大小就是最小组数。
int main(){
scanf("%d",&n);
vector<point> range;
for(int i = 0;i<n;i++){
int a,b;
scanf("%d%d",&a,&b);
range.push_back({a,b});
}
sort(range.begin(),range.end()) ;
priority_queue<int,vector<int>,greater<int>> q;//构建小根堆 堆顶元素为上个会议的右端点
for(int i = 0;i<n;i++){
if(!q.empty()&&range[i].l>q.top())//如果不为空,且当前区间的左值>堆顶,则复用
q.pop();//更新右端点;
q.push(range[i].r); //把当前区间右端点压入
}
printf("%d\n",q.size());
return 0;
}
题目3 区间覆盖问题

贪心策略;每次选左起点能覆盖当前节点、且右端点是最大的区间
思路:1.先将节点按左端点排序 2.贪心循环每次找其右端点大的
cpp
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
int n, st,ed;
struct node {
int l,r;
bool operator<(const node &w)const{
return l<w.l;
}
};
int main(){
scanf("%d%d",&st,&ed);
scanf("%d",&n);
vector<node> range;
for(int i = 0;i<n;i++){
int a,b;
cin>>a>>b;
range.push_back({a,b});
}
sort(range.begin(),range.end());
int res = 0;
bool success = false;
for(int i = 0;i<n;i++){
int j = i ,r =-2e9;
while(j<n&&range[j].l<=st){//找左端小于st,但右端大的节点 ,贪心在于 一直找右侧大的区间
r = max(r,range[j].r);
j++;
}
if(r<st){
res = -1;
break;
}
res++;//区间数加1
if(r>=ed){
success =true;
break;
}
st = r;
i = j-1; //本质:在内层循环处理完一批区间后,通过手动设置 i 的值,配合外层 for 循环的i++
//到下一个未处理的区间,避免重复计算,保证算法高效。
}
if(!success)
cout<<"-1";
else
cout<<res;
return 0;
}
区间覆盖问题 : P2242 公路维修问题 - 洛谷
贪心策略:优先选取较长的距离/空隙进行求解 ,每步最优,全局最优
题意:
1.区间总长度:拆成m-1段最大间距
计算相邻点的间距,降序排序后,优先拆分最大的m-1个间距,每次拆分能减少(间距-1)的总长度。
cpp
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
const int N =20010;
int n,m,ans;
int a[N];//每个坑的距离
int l[N];//存最大的距离
bool cmp(int k,int h){
return k>h;//从大到小排序
}
int main(){
scanf("%d%d",&n,&m);
for(int i = 1;i<=n;i++){
scanf("%d",&a[i]);
}
for(int i= 1;i<n;i++){
l[i] = a[i+1]-a[i]; //每个节点的距离
}
sort(l+1,l+n,cmp);
ans = a[n]-a[1]+1;//原始长度
for(int i = 1;i<m;i++){
ans= ans-l[i]+1;//从中间减去不需要的长度
}
printf("%d\n",ans);
return 0;
} //贪心策略:要总长度最小, 每次优先处理较长的距离/空隙
