贪心算法专题
Posted 算法修炼
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了贪心算法专题相关的知识,希望对你有一定的参考价值。
概论
贪心算法是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出的是在某种意义上的局部最优解。
说简单点就是可大的拿,往死里塞
例一
题目描述
有⼀个⼈有 n 门课程,每⼀门课程他最多获得 r 学分,他只要所有课程的平均学分⼤等于 avg,他就可以获得奖学金。每门课程,他已经获得了 ai 学分,剩下的每⼀个学分,都需要写 bi 篇论⽂才能得到,然后问你,这个⼈最少写多少论⽂才能获得奖学金。(1 ≤ n ≤ 1e5, 1 ≤ r ≤ 1e9, 1 ≤ avg ≤ min(r, 1e6))
问题分析
首先对b[i]的值进行排序,贪心选择最小的b[i]值,直到满足大于科目平均值的条件为止。
AC代码
#include<iostream>
using namespace std;
#include<bits/stdc++.h>
typedef long long ll;
const int N = 1e5+5;
struct node{
int a;
int b;
}g[N];
bool cmp(node x,node y){
return x.b < y.b;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
int n,r,avg;
ll sum = 0;
ll cnt;
while(cin>>n){
cin>>r>>avg;
for(int i = 0;i < n;i++){
cin>>g[i].a>>g[i].b;
sum += g[i].a;
}
sort(g,g+n,cmp);
sum = avg*n - sum;
cnt = 0;
for(int i = 0;i < n;i++){
if(sum <= 0){
break;
}
if(g[i].a < r){
if((sum-r+g[i].a) >0){
sum -= (r-g[i].a);
cnt += (r-g[i].a)*g[i].b;
}
else{
cnt += sum*g[i].b;
break;
}
}
}
cout<<cnt<<endl;
}
return 0;
}
例二
题目描述
给 n 棵树在⼀维数轴上的坐标,以及它们的⾼度。现在要你砍倒这些树树,可以向左倒也可以向右倒,砍倒的树不能重合、当然也不能覆盖其他的树原来的位置,现在求最⼤可以砍倒的树的数目。
1 ≤ xi, hi ≤ 1e9
问题分析
贪心法:第⼀棵树的左边和最后⼀棵树的右边没树,所以他们向两边倒, 然后对于中间的树来说,首先先向左边倒,然后左边距离如果不 够的话再向右边倒。
AC代码
#include<iostream>
using namespace std;
#include<bits/stdc++.h>
typedef long long ll;
const int N = 1e5+5;
int dis[N];
int x[N];
int h[N];
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
int n;
while(cin>>n){
if(n == 1){
cout<<"1"<<endl;
return 0;
}
for(int i = 0;i < n;i++){
cin>>x[i]>>h[i];
}
dis[0] = 0;
int cnt = 2;
for(int i = 1;i < n;i++){
dis[i] = x[i]-x[i-1];
}
for(int i = 1;i < n-1;i++){
if(h[i] < dis[i]){
cnt++;
}
else if(h[i] >= dis[i] && h[i] < dis[i+1]){
cnt++;
dis[i+1] -= h[i];
}
}
cout<<cnt<<endl;
}
return 0;
}
例三
题目描述
给⼀个长为 n 的序列,以及交换次数 k,每次可以在原先的序列中任意交换两个数交换后找⼀个最⼤⼦串和,输出其可能的最⼤值。
1 ≤ n ≤ 200, 1 ≤ k ≤ 10
问题分析
首先暴力遍历子序列的位置,然后贪心,将子序列中的最小值和子序列外的最大值进行满足条件的交换即可。
AC代码
#include<iostream>
using namespace std;
#include<bits/stdc++.h>
typedef long long ll;
const int N = 205;
const int INF = 1e9+7;
int a[N],b[N],c[N];
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
int n,k;
cin>>n>>k;
int ans = -INF;
for(int i = 1;i <= n;i++){
cin>>a[i];
}
for(int i = 1;i <= n;i++){
for(int j = i;j <= n;j++){
int l1 = 0,l2 = 0,cnt = 0;
for(int x = 1;x <= n;x++){
if(x >= i && x <= j){
b[l1++] = a[x];
cnt += a[x];
}
else{
c[l2++] = a[x];
}
}
sort(b,b+l1);
sort(c,c+l2,greater<int>());//从大到小排序
for(int x = 0;x < min(l1,min(l2,k));x++){
if(c[x] > b[x]){
cnt += (c[x]-b[x]);
}
else{
break;
}
}
if(ans < cnt){
ans = cnt;
}
}
}
cout<<ans<<endl;
return 0;
}
例四
问题描述
AB 都是 n*m 的矩阵,已知 B 矩阵是由 A 矩阵以⼀种规则⽣成,Bij 是由 A 矩阵的第 i ⾏的所有元素和第 j 列的所有元素或运算得到给定 B 矩阵,求是否存在⼀个矩阵 A 能⽣成 B。
1 ≤ n, m ≤ 100
问题分析
由或运算可知,Bij = 0时当且仅当A的第i 和 j 列的所有元素都为0。其他情况Bij的值都为1.因此可以先将Aij的值都赋值为1,然后根据Bij的值将对应的行列全部赋值为0。最后将得到的A矩阵按规则进行或运算,判断是否可以得到B矩阵。
AC代码
#include<iostream>
using namespace std;
#include<bits/stdc++.h>
typedef long long ll;
const int N = 105;
const int INF = 1e9+7;
int a[N][N],b[N][N];
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
int n,m;
cin>>n>>m;
for(int i = 1;i <= n;i++){
for(int j = 1;j <= m;j++){
cin>>b[i][j];
a[i][j] = 1;
}
}
for(int i = 1;i <= n;i++){
for(int j = 1;j <= m;j++){
if(!b[i][j]){
for(int k = 1;k <= m;k++){
a[i][k] = 0;
}
for(int k = 1;k <= n;k++){
a[k][j] = 0;
}
}
}
}
for(int i = 1;i <= n;i++){
for(int j = 1;j <= m;j++){
if(b[i][j]){
int f = 0;
for(int k = 1;k <= m;k++){
f |= a[i][k];
}
for(int k = 1;k <= n;k++){
f |= a[k][j];
}
if(!f){
cout<<"NO"<<endl;
return 0;
}
}
}
}
cout<<"YES"<<endl;
for(int i = 1;i <= n;i++){
for(int j = 1;j <= m;j++){
cout<<a[i][j]<<" ";
}
cout<<endl;
}
return 0;
}
例五
问题描述
有 n 个曲⼦,每个曲⼦的范围为 [ai, bi]。有 m 个演奏家,每个演奏家的范围为 [ci, di],可以出演 ki 次如果 ci ≤ ai ≤ bi ≤ di,则说明该曲⼦可以由演奏家演出构造⼀个⽅案使得所有曲⼦都能被演奏,⽆⽅案输出”NO”
问题分析
按照右端点排序,每⼈⼈优先选取能⼒之内的左端点最小的歌曲用 set 维护贪⼼
AC代码
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1000005;
struct node{
int l,r,pos,k;
};
node part[maxn],actor[maxn];
bool cmp(node a,node b){
return a.r<b.r;
}
int ans[maxn];
set<pair<int,int> >s;
int main(){
s.clear();
memset(ans,0,sizeof(ans));
int n,m;
cin>>n;
for(int i=1;i<=n;i++){
scanf("%d%d",&part[i].l,&part[i].r);
part[i].pos=i;
}
cin>>m;
for(int i=1;i<=m;i++){
scanf("%d%d%d",&actor[i].l,&actor[i].r,&actor[i].k);
actor[i].pos=i;
}
sort(part+1,part+1+n,cmp);
sort(actor+1,actor+1+m,cmp);
int i=1,j=1,ok=0;
while(j<=m){
if(i<=n && part[i].r<=actor[j].r){
s.insert(make_pair(part[i].l,part[i].pos));
i++;
}
else{
set<pair<int,int> >::iterator it;
it=s.lower_bound(make_pair(actor[j].l,0));
if(it==s.end()){
if(j==m && s.size()>0){//如果s不为空,并且最后一个演员没有歌剧演说明扔到s里面的某些歌剧没人演
ok=1;
break;
}
else{
j++;
continue;
}
}
ans[it->second]=actor[j].pos;
s.erase(it);
actor[j].k--;
if(actor[j].k==0)j++;//只有当这个演员不能再用的时候再考虑下一个演员
}
}
for(int i=1;i<=n;i++)if(ans[i]==0)ok=1;
if(!ok){
cout<<"YES"<<endl;
cout<<ans[1];
for(int i=2;i<=n;i++)printf(" %d",ans[i]);
cout<<endl;
}
else{
cout<<"NO"<<endl;
}
}
例六
问题描述
给长为 n 的数列,每次可以删⼀个数,得到这个数左右两个数的最小值的权值(如果左右两侧不全,权值为 0),问最⼤权值和。
1 ≤ n ≤ 5 e5
问题分析
贪心的想,每次如果一个数不小于它相邻的两个数,那么这个数就可以删去,得到的分数就是min(a,b),最后剩下的就是一个倒v形状的或者一半,最高的那两个选不了,再把剩下的相加就好了
AC代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> P;
const int N = 5e5+5;
int a[N],b[N],n;
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cin>>n;
for(int i = 1;i <= n;i++){
cin>>a[i];
}
ll ans = 0;
b[1] = a[1];
int cnt = 1;
for(int i = 2;i <= n;i++){
while(b[cnt] <= a[i] && b[cnt]<=b[cnt-1] && cnt>1){
ans += min(b[cnt-1],a[i]);
cnt--;
}
b[++cnt] = a[i];
}
sort(b,b+cnt+1);
for(int i = 1;i < cnt-1;i++){
ans += b[i];
}
cout<<ans<<endl;
return 0;
}
以上是关于贪心算法专题的主要内容,如果未能解决你的问题,请参考以下文章