贪心
一.概念
1)官方解释
贪心法是一种解决最优问题的策略。它是从问题的初始解出发,按照当前最佳的选择,把问题归纳为更小的相似的子问题,并使子问题最优,再由子问题来推导出全局最优解。
使用贪心方法需要注意局部最优与全局最优的关系,选择当前状态的局部最优并不一定能推导出问题的全局最优。
实际生活中,经常需要求一些问题的“可行解”和“最优解”,这就是所谓的“最优化”问题。
一般来说,每个最优化问题都包含一组“限制条件”和一个“目标函数”,符合限制条件的问题求解方案称为可行解,使目标函数取得最佳值(最大或最小)的可行解称为最优解。
求解最优化问题的算法很多,例如穷举、搜索、动态规划等。贪心法也是求解这类问题的一种常用方法。
2)个人理解
贪心是一种解题方法与步骤,在初级题目里,贪心思想是拿分的好手
见到一道疑似贪心的题,尝试用自己的方式证明,通过灵活运用给出的样例,推演规则,同时在可能的情况下添加更多特殊解法与判别
同一道题可能有不同的贪心策略
就例如双人过桥这道题,在无法确定是在快的一个人往返还是两个人往返时,只要时间复杂度允许,完全可以将两种方法比较去最优解,事实证明2o(n)的复杂度是完全够用的
在接触到一些高级的题目时
我们也可以运用贪心来剪枝
所谓贪心只能过样例
在想拿更高的分的情况下又怕超时
例如使用递归时
我们就可以通过贪心直接舍弃一些 不可能的点
加快运行速度
一石二鸟
3)步骤
a、确定贪心策略
b、根据贪心策略,一步一步得到局部最优解
c、将局部最优解合并起来就得到全局最优解
4)特点
1.贪心选择性质:所求问题的整体最优解可以通过一系列局部最优的选择来达到,这样的选择称为贪心选择。这些选择只依赖于以往所做过的选择,决不依赖于将来的选择。
2.最优子结构性质:当一个问题的最优解包含其子问题的最优解时,称此问题具有最优子结构性质。
二.例题及延伸证明&优化方法
1.国王的游戏
1)思路
在见到这道题的时候,明显直接循环是会超时的
并且int会超范围,必须用高精度方可
但最最最最重要的不是这些细枝末节
而是贪心方法的证明
到底哪个为重?
借用老师的一句话:看哪个部分对答案影响最大
但我还是没有证明的方法
于是我找到了题解。。。
看了眼数论证明
搞懂了
结论是:al * ar<bl * br
2)60分代码
因为
本蒟蒻不太会用高精度QWQ
所以只拿了
60分
不过大体方案是对的
#include<bits/stdc++.h>
using namespace std;
int n;
const int maxn=21474836;
int x,y;
struct HW
{
long long l;
long long r;
long long qzh;
long long price;
long long prepare;
}a[maxn];
bool mycmp(HW p,HW q)
{
return p.prepare<q.prepare;
}
void init()
{
cin>>n;
cin>>x>>y;
for(int i=1;i<=n;i++)
{
cin>>a[i].l>>a[i].r;
a[i].prepare=a[i].l*a[i].r;
}
}
int main()
{
freopen("game.in","r",stdin);
freopen("game.out","w",stdout);
/*前面的所有人的左手上的数的乘积除以他自己右手上的数,然后向下取整得到的结果。*/
init();
sort(a+1,a+n+1,mycmp);
// for(int i=1;i<=n;i++)
// cout<<a[i].l<<' '<<a[i].r<<endl;
a[0].qzh=x;
for(int i=1;i<=n;i++)
{
a[i].qzh=a[i-1].qzh*a[i].l;
a[i].price=a[i-1].qzh/a[i].r;
}
// for(int i=1;i<=n;i++)
// cout<<a[i].qzh<<endl;
int maxx=-1;
for(int i=1;i<=n;i++)
{
if(a[i].price>maxx) maxx=a[i].price;
}
cout<<maxx;
return 0;
}
注释就不详细加了
但大体的方案是:先整体输入记录左右手乘积的值,并借此排序
为了节省时间乘积用了== 前缀和 ==不懂得可以看我前天的博客 前缀和应用
3)应用方案—对数证明
如下
2.最大的子序列和
1)思路
用一个sum记录当前前缀和,一路累积过去,如果前缀和sum变成了负数,那么下一个数就不需要前面的数了(因为还不如只选它一个),这时把sum置为0,再继续累加。
2)代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=10000100;
int n,a[maxn];
int maxx=-maxn,sum=0;
int main()
{
freopen("maxsum.in","r",stdin);
freopen("maxsum.out","w",stdout);
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
for (int i=1;i<=n;i++)
{
sum+=a[i+1];
if (sum>maxx) maxx=sum;
if (sum<0) sum=0;
}
if(maxx==0) cout<<a[n];
else cout<<maxx;
return 0;
}
3)延伸方法–尺取法
4)延伸例题–最短自序列
<1> 思路
根据尺取法的特性,判断左右区间下标求解
<2> 代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=10010;
bool flag=true;
int main()
{
freopen(",in","r",stdin);
freopen(",out","w",stdout);
int n;
cin>>n;
for(int i=n;i>=1;i--)
{
int N,S,a[maxn];
int ans=N+1,s=1,t=1,sum=0;
cin>>N>>S;
for(int j=1;j<=N;j++)
{
cin>>a[j];
}
while(flag)
{
while(t<=N&&sum<S)
{
sum+=a[t++];
}
if(sum<S)
{
break;
}
ans=min(ans,t-s);
sum-=a[s++];
}
if(ans>N)
{ans=0;}
cout<<ans<<endl;
}
}
3.最大整数
1).思路
很显然,一见面就知道是到贪心题目
而策略呢?
第一眼见到发现其优先比较方法与字符串相差不多
也就是所谓的字典排序
先排列首位
而之后的则需要改变一下
举一个反例
2 个数:9,91
显然字符串直接比较会把91放在前面
而在总数位不变的情况下
靠前的数位越大越好
所以我们要自己增加sort函数的判断
我的想法是全补上0判断
但代码较为繁琐
如下
2).代码
#include<bits/stdc++.h>
using namespace std;
int n;
string s[25];
bool pd(string a,string b)
{
int hy,hx;
hy=a.size(),hx=b.size();
int t=max(hy,hx);
if(t==hy)
{
for(int i=hx+1;i<t;i++)
{
b[i]=0;
}
}
else
{
for(int i=hy+1;i<t;i++)
{
a[i]=0;
}
}
return a>b;
}
bool cmp(string a,string b)
{
if(a[0]!=b[0]) return a>b;
else return a+b>b+a;
}
int main()
{
freopen("max.in","r",stdin);
freopen("max.out","w",stdout);
cin>>n;
for(int i=0;i<n;i++)
{
cin>>s[i];
}
sort(s,s+n,/*pd*/cmp);
for(int i=0;i<n;i++)
cout<<s[i];
return 0;
}
总结
贪心不仅要“贪”,更要用“心”
在拿到题时不能盲目求解
想好策略与方法
在代码运行时间足够的前提下其实更因该谨慎使用
自己动手推出来的步骤才是最保险的!!