1.所谓贪心
就是每一步的最优解可以不断递推到后面的最优解,小部分的最优解推出大部分的最优解:
可以这样想,每一次求解要如何计算才最好,我们每次选最好,就是每次选最贪,贪心地去选;所以叫贪心
例题:
硬币问题:
有1 5 10 50 100 500元硬币,如何换使得用硬币少?
比如输入
3 2 1 3 0 2 620
输出 6(500 一个 50 2个 10 一个 5 2个 );
思路:
每次贪心都选最大地面额去兑换:
直到结束;
模拟如果当前有这个硬币并且这个硬币小于等于当前要兑换的值就换
代码:
#include <iostream>
using namespace std;
const int v[6]={
1,5,10,50,100,500};
int w[6];
int key,ans=0;
int main()
{
for(int i=0;i<6;++i)
{
cin>>w[i];
}
cin>>key;
for(int i=5;i>=0;--i)//因从最大开始慢慢选,那么就从最后一个开始往前选
{
while(key>=v[i]&&w[i]!=0)//如果当前的值要可以用硬币兑换,而且还有硬币剩余
{
key-=v[i];
ans++;
w[i]--;
}
}
cout<<ans;
}
法二:
我们这样想如果可以选的话,直接一次性拿走最多能拿的
key/当前硬币大小,这个是当前该面额可以拿的最多数量,但是我们有的时候银币数不够,所以就拿走2者较小者
代码:
、#include <iostream>
using namespace std;
const int v[6]={
1,5,10,50,100,500};
int w[6];
int key,ans=0;
int main()
{
for(int i=0;i<6;++i)
{
cin>>w[i];
}
cin>>key;
for(int i=5;i>=0;--i)//因从最大开始慢慢选,那么就从最后一个开始往前选
{
int t=min(key/v[i],w[i]);//选取2者较小者
key-=t*v[i];
ans+=t;
}
cout<<ans;
}
例题:123
思路:找出最多出现的,总数减去就好了
代码
#include <iostream>
using namespace std;
int a[10010];
int main()
{
int n,x;
cin>>n;
for(int i=1;i<=n;++i)
{
cin>>x;
a[x]++;
}
int max=0;
for(int i=1;i<=3;++i)
{
if(a[i]>max)
{
max=a[i];
}
}
cout<<n-max<<endl;
}
注意点:有的时候贪心要特别判断一下0啊,1啊什么的情况
2.区间调度问题:
例题:yyy的线段问题
思路:
尽可能选取最多的点该如何选取呢:
贪心的思想:
这时候有2种考虑的点:
开头和结尾;
如果每次选取开头最早的话 应该是不行的
你可以想想如果开头最早但是这个任务很长,中间有许多短小精悍的事件,这样是错误的:
第二考虑每次结束最早的这个是正确的;
我们可以这样证明:
如果不选这个选其他的,那么选这个任务没差,而且选这个任务数量还可以增加
代码;
/*
1.输入数据
2.按照尾巴时间结束的大小排序
小的在前面:
3.每次选取当前可以选的
记录尾巴
*/
#include <iostream>
#include <algorithm>
using namespace std;
const int maxn=1e5;
struct node{
int st,sp;
};
node a[maxn];
bool cmp(node a,node b)
{
return a.sp<b.sp;
}
int main()
{
int n;
cin>>n;
for(int i=0;i<n;++i)
{
cin>>a[i].st>>a[i].sp;
}
sort(a,a+n,cmp);
int ans=1,cur=a[0].sp;//选取当前最后一个点
for(int i=1;i<n;++i)
{
if(cur<=a[i].st)
{
cur=a[i].sp;//记录当前最后一个点
ans++;
}
}
cout<<ans<<endl;
return 0;
}
总结:区间问题有2个点可以考虑开头和结尾慢慢考虑
例题:鸿山洞的灯
思想:
从前向后排序:
如果前面的灯开着可以把当前的灯找亮就全部删除区间里面的灯,ans++;注意判断前面灯要亮着;
前面的灯是暗的,就从当前往前找,找到第一个亮的灯,判断该位置是不是再这个亮的灯范围里面是的话就关了,ans++;
代码:
#include <iostream>
#include <algorithm>
using namespace std;
int p[10010];
bool b[10010];
int main()
{
int n,dist,ans=0;
cin>>n>>dist;
for(int i=0;i<n;++i)
{
cin>>p[i];
b[i]=true;
}
sort(p,p+n);
for(int i=1;i<n-1;++i)
{
if(b[i-1]!=0&&p[i+1]-p[i-1]<=dist) b[i]=0,ans++;
else
{
int j=i-1;
while(!b[j]) j--;
if(p[i+1]-p[j]<=dist) b[i]=0,ans++;
}
}
cout<<ans<<endl;
return 0;
}
技巧总结:
这里为什么不枚第一个和最后一个灯呢,答案是显然的因为这2个一定要开着;
用一个b数组来记录这个灯是否开着,就可以不用移动灯了;和我之前写过的约瑟夫问题异曲同工之妙;
例题2:监控
思路一:每一次能跑就跑,贪心跑:
代码:
/*
每次行动到1这个位置等一下,变化
继续行动,每次行动ans++;
6
0 0 1 1 0 1
i=3 ans=1;
1 1 0 0 1 0
i=5,ans=2;
0 0 1 1 0 1
i=6,ans=3;
*/
#include <iostream>
using namespace std;
int a[10010];
int main()
{
int n,ans=0,j=1;//位置在1.结束就是位置出去了
cin>>n;
for(int i=1;i<=n;++i)
{
cin>>a[i];
}
while(j<=n)
{
while(a[j]==0) j++;//如果没监控,直接一直跑
ans++;//然后ans++;
for(int i=1;i<=n;++i)
{
a[i]=!a[i];//全部改变一下位置
}
}
cout<<ans<<endl;
return 0;
}
方法二:
刚刚的算法我们发现时间复杂度太高了,改进一下:
我们发现什么时候回停下来,就是前后不一样的时候,那么我们其实不用改什么,就遇到前后不一样,答案加一加上去就好了:
于是
/*
每次行动到1这个位置等一下,变化
继续行动,每次行动ans++;
6
0 0 1 1 0 1
i=3 ans=1;
1 1 0 0 1 0
i=5,ans=2;
0 0 1 1 0 1
i=6,ans=3;
*/
#include <iostream>
#include <cstdio>
using namespace std;
int a[1000005],n;
int main()
{
int n,ans=1;//位置在1.结束就是位置出去了
scanf("%d",&n);
for(int i=1;i<=n;++i)
{
scanf("%d",&a[i]);
if(a[i]!=a[i-1]) ans++;
}
printf("%d\n",ans);
return 0;
}
3.字典符贪心:
下次更新把,有点难