目录
贪心算法属于短视算法,其代码一般都较为简洁,难在证明
刷题记录
10.28号忍着肚饿将贪心刷完,接下来7天后再刷一遍
区间问题
涉及区间问题一般都需要先排序
Acwing905区间选点问题
地址:
https://www.acwing.com/problem/content/907/
问题描述:
思路解析
https://www.acwing.com/solution/content/16905/
解题步骤:
1、将每个区间按右端点从小到大进行排序
2、从前往后枚举每个区间,初始选定ed值为无穷小
3、若当前区间中包含该点end,则直接跳过
4、否则,选择当前区间的右端点
为什么ed初始值无穷小?
区间的取值范围:
因为我们知道初始条件下,我们还没有取点,而取点的条件为:
为了使该条件在第一次判定时恒成立,所以ed要取无限小。
证明:
(1)找到cnt个点,满足题意情况,则最优解Ans <= cnt
(2)找到cnt个点,即找到cnt个区间,且区间从左到右依次排好,且没有相同的交集,则说明可能有区间没有被这cnt个点覆盖过,所以最优解Ans >= cnt
则Ans == cnt,证毕
代码
#include <iostream>
#include <algorithm>
using namespace std;
int n;
const int N=100010;
//定义区间结构体
//重载运算符<,需要根据区间的右端点进行升序排序
struct Range{
int l,r;
bool operator <(const Range &w)const{
return r<w.r;
}
}range[N];
int main(){
cin>>n;
for(int i=0;i<n;i++){
int l,r;
cin>>l>>r;
range[i].l=l;
range[i].r=r;
}
//对range区间根据左端点大小进行升序排序
sort(range,range+n);
//res答案数量,ed已经确定的区间右端点
int res=0,ed=-2e9;
for(int i=0;i<n;i++){
//range[i].l>ed说明ed这个点没有被任何区间所包含,需要添加一个ed点
if(range[i].l>ed){
res++;
ed=range[i].r;
}
}
cout<<res<<endl;
}
Acwing908最大不相交区间数量(区间选点问题的另一种说法)
该问题可以实例化为在时间不冲突的条件下,选尽可能多的课
地址:
https://www.acwing.com/problem/content/910/
描述:
思路:
与区间选点问题类似
代码:
#include <iostream>
#include <algorithm>
using namespace std;
int n;
const int N=100010;
//定义区间结构体
//重载运算符<,需要根据区间的右端点进行升序排序
struct Range{
int l,r;
bool operator <(const Range &w)const{
return r<w.r;
}
}range[N];
int main(){
cin>>n;
for(int i=0;i<n;i++){
int l,r;
cin>>l>>r;
range[i].l=l;
range[i].r=r;
}
//对range区间根据左端点大小进行升序排序
sort(range,range+n);
//res答案数量,ed已经确定的区间右端点
int res=0,ed=-2e9;
for(int i=0;i<n;i++){
//range[i].l>ed说明ed这个点没有被任何区间所包含,需要添加一个ed点
if(range[i].l>ed){
res++;
ed=range[i].r;
}
}
cout<<res<<endl;
}
Acwing906区间分组
可以实例化为将有矛盾的小朋友分开,且分最少的组
有若干个活动,第i个活动开始时间和结束时间是[Si,fi],同一个教室安排的活动之间不能交叠,求要安排所有活动,少需要几个教室?
地址:
https://www.acwing.com/problem/content/908/
描述:
思路:
1、将所有区间按左端点从小到大排序
2、从前往后枚举每个区间,判断能否将其放到某个现有的组中,即是否存在当前区间的的左端点L > 任意组中右端点的最小值的组
如果不存在这样的组,则开新组,然后再将其放进组中
如果存在这样的组,则将其放在符合条件的组中,并更新当前组的右端点的值
3、为了不用每次选择组时都遍历所有组,可以通过小根堆来维护所有组中的尾端
代码:
#include <iostream>
#include<algorithm>
#include <queue>
using namespace std;
int n;
const int N=1e5+10;
struct Range{
int l,r;
//按照区间的左端点进行升序拍序
bool operator <(const Range &W)const{
return l<W.l;
}
}range[N];
int main(){
cin>>n;
for(int i=0;i<n;i++){
int l,r;
cin>>l>>r;
range[i].l=l;
range[i].r=r;
}
//首先按照左端进行升序拍序
sort(range,range+n);
//定义小根堆,存放每组右边界的最大值
priority_queue<int, vector<int>, greater<int>> q;
for(int i=0;i<n;i++){
//在小根堆为空或是区间的左端点比任意一组区间的最大值都小时需要增加一组
if(q.empty()||range[i].l<=q.top()){
//新开一组
q.push(range[i].r);
}else {
//range[i].l>=q.top()
//否则说明存在一组可以将该区间放入,随便找一组放入
//这里我们将最小的去掉,
//因为range[i].r要么比最小值小,要么比最小值大。但加入后都起到了更新最小值的作用
q.pop();
q.push(range[i].r);
}
}
cout<<q.size()<<endl;
return 0;
}
Acwing907区间覆盖
地址
https://www.acwing.com/problem/content/909/
描述
思路
代码
#include <iostream>
#include <algorithm>
using namespace std;
int n;
const int N=1e5+10;
struct Rangle{
int l,r;
bool operator <(const Rangle &W)const{
return l<W.l;
}
}rangle[N];
int main(){
int st,ed;
cin>>st>>ed;
cin>>n;
for(int i=0;i<n;i++){
int l,r;
cin >> rangle[i].l >> rangle[i].r;
}
//先排序
sort(rangle,rangle+n);
//根据二分法找到包含start端点且右端点尽可能大的区间
bool success=false;
int res=0;
for(int i=0;i<n;i++){
int j=i;
int r=-2e9;
while(j<n&&rangle[j].l<=st){
//不能写成r=rangle[j].r,因为可能这个区间包含了st点但是它特别短不是右边界最长的
r=max(r,rangle[j].r);
j++;
}
// 如果某次循环+更新下来,r不在start的右边,直接就说明不能完全覆盖
if(r<st){
res=-1;
break;
}
res++;
if(r>=ed){
success=true;//因为最后找到r可能还是小于ed的
break;//真正的找到答案
}
i=j-1;
st=r;//更新下一次的st,用r更新。
}
if(success) cout<<res<<endl;
else cout<<-1<<endl;
return 0;
}
Huffman树问题
Acwing148合并果子
注意与动态规划中区间DP的合并石子问题进行区分,合并石子问题要求只能合并相邻的两堆石子
题解地址:https://blog.csdn.net/qq_52934831/article/details/120102264?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522163503656416780269871041%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=163503656416780269871041&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2blogfirst_rank_v2~rank_v29-1-120102264.pc_v2_rank_blog_default&utm_term=%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92&spm=1018.2226.3001.4450
地址
https://www.acwing.com/problem/content/150/
描述
思路
每次将最小的两堆合并
代码
//求解最小费用问题
//假如可以合并任意两堆石子,那么可以直接采用贪心算法求解
#include <iostream>
#include <algorithm>
#include <queue>
using namespace std;
int n;//表示石堆数目
const int N=310;
int Count=0;//代价
int main(){
cin>>n;
priority_queue<int,vector<int>,greater<int>>q;//定义小根堆
//将堆全放入小根堆中
for(int i=0;i<n;i++){
int w;
cin>>w;
q.push(w);
}
//每次将质量最小的两堆合并
while(q.size()>1){
int s1=q.top();
q.pop();
int s2=q.top();
q.pop();
int s3=s1+s2;
Count+=s3;
//别忘了将合并好的堆放入小根堆中
q.push(s3);
}
cout<<Count<<endl;
return 0;
}
排序不等式
Acwing913排队打水
地址
https://www.acwing.com/problem/content/description/915/
描述
思路
代码
#include <iostream>
#include <algorithm>
typedef long long LL;
using namespace std;
int n;
const int N=1e5+10;
int t[N];
int main(){
cin>>n;
for(int i=1;i<=n;i++){
int time;
cin>>time;
t[i]=time;
}
//将数组从小到大进行排序
sort(t+1,t+n+1);
//等待总时间,不能定义成int超界
LL w=0;
for(int i=1;i<=n;i++){
w+=t[i]*(n-i);
}
cout<<w<<endl;
return 0;
}
绝对值不等式
Acwing104货舱选址
地址
https://www.acwing.com/problem/content/106/
描述
思路
中位数定义:
把所有的同类数据按照大小的顺序排列。
如果数据的个数是奇数,则中间那个数据就是这群数据的中位数;
如果数据的个数是偶数,则中间那2个数据的算术平均值就是这群数据的中位数。
代码
#include <iostream>
#include <algorithm>
typedef long long LL;
const int N=1e5+10;
using namespace std;
int n;
int A[N];
LL res=0;
int main(){
cin>>n;
for(int i=1;i<=n;i++){
int a;
cin>>a;
A[i]=a;
}
//寻找中位数先排序
sort(A+1,A+n+1);
if(n%2==0){
//偶数
for(int i=1;i<=n;i++){
if(i<=n/2) res-=A[i];
else res+=A[i];
}
}
else {
for(int i=1;i<=n;i++){
if(i<n/2+1) res-=A[i];
if(i>n/2+1) res+=A[i];
}
}
cout<<res;
return 0;
}
推公式
Acwing125耍杂技的牛
地址
https://www.acwing.com/problem/content/127/
描述
思路
代码
#include <iostream>
#include <algorithm>
using namespace std;
int n;
const int N=50010;
typedef pair<int ,int > PII;
PII cow[N];
int main(){
cin>>n;
for(int i=0;i<n;i++){
int w,s;
cin>>w>>s;
cow[i]={
w+s,s};
}
//将奶牛按照w+s升序排序,pair排序按照first为第一标准
sort(cow,cow+n);
//依次计算奶牛的危险系数取最大值
//res是奶牛危险系数的最大值,sum是奶牛上面的总质量
int res=-2e9,sum=0;
for(int i=0;i<n;i++){
int s=cow[i].second,w=cow[i].first-s;
res=max(res,sum-s);
sum+=w;
}
cout<<res;
return 0;
}