这种学习重点是贪心和动态规划。贪心算法是一种将解题过程分为许多步,每一步都寻找最优解的算法,目光短浅,只能寻找到一步的解法,因此需要在解题时仔细判断贪心标准是否正确。贪心算法的证明比较困难,因此使用时着重寻找是否存在漏洞反例。
先来一道简单的贪心
【深基12.例1】部分背包问题 - 洛谷
这道题是背包问题的一种但是不一样在可以将物品分割,很容易就能想到贪心标准是物品的性价比,在深思熟虑确定没有什么反例之后直接用贪心写出代码。
#include<iostream>
using namespace std;
int n;
double t,v[101],w[101],a[101],ans;
int main()
{
cin>>n>>t;
for(int i=1;i<=n;i++)
{
cin>>w[i]>>v[i];
a[i]=v[i]/w[i];
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<n;j++)
{
if(a[j]<a[j+1])
{
swap(a[j],a[j+1]);
swap(v[j],v[j+1]);
swap(w[j],w[j+1]);
}
}
}
for(int i=1;i<=n;i++)
{
if(t-w[i]>0)
{
t-=w[i];
ans+=v[i];
}
else
{
ans+=t*a[i];
break;
}
}
cout<<ans;
}
再来一道有意思的
酥油医生正在教起亚如何计算两个整数的总和。但起亚是如此粗心大意,当两位数的总和超过9时,总是忘记携带一个数字。例如,当她计算 4567+5789 时,她将获得 9246,对于 1234+9876,她将获得 0。Ghee对此很生气,给她带来了一个很难解决的问题:
现在起亚有两个整数A和B,她可以随心所欲地洗牌每个数字中的数字,但不允许使用前导零。也就是说,对于A = 11024,她可以将数字重新排列为10124,或41102,或许多其他,但02411是不允许的。
在她洗牌A和B之后,她会以自己的方式将它们加在一起。A“+”B的最大可能总和是多少?
刚看到这道题的时候我第一个想法就是暴搜,每次都找以这种方式加起来最大的,放到最靠前的那一位,删除之后继续搜,直到将所有数字处理完,感觉思路应该没错,就是时间不知到过不过得去,后来看了看大佬的解题,大佬不愧是大佬
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;
const int MAXN=2*1e6+100;
char s1[MAXN],s2[MAXN];
int ans[MAXN];
int cnt1[20],cnt2[20];
int main()
{
int cas;
scanf("%d",&cas);
for(int k=1;k<=cas;k++)
{
memset(cnt1,0,sizeof(cnt1));
memset(cnt2,0,sizeof(cnt2));
memset(ans,0,sizeof(ans));
scanf("%s%s",s1,s2);
int len=strlen(s1);
if(len==1)
{
int a=s1[0]-'0',b=s2[0]-'0';
printf("Case #%d: %d\n",k,(a+b)%10);
continue;
}//初始化数据
for(int i=0;i<len;i++) cnt1[s1[i]-'0']++,cnt2[s2[i]-'0']++;//记录数据的个数
int p=0,q=0,tt=-1;
for(int i=1;i<=9;i++)
for(int j=1;j<=9;j++)
if(cnt1[i]&&cnt2[j])
{
if((i+j)%10>tt) tt=(i+j)%10,p=i,q=j;
}//先寻找到一个最大的或最小的作为首位
ans[1]=tt;
int tot=1;
cnt1[p]--,cnt2[q]--;//去除使用过的元素
for(int l=9;l>=0;l--)
{
for(int i=0;i<=9;i++)
{
if(!cnt1[i]) continue;
int p;
if(i<=l)
p=l-i;
else p=l-i+10;
int q=min(cnt1[i],cnt2[p]);
cnt1[i]-=q;
cnt2[p]-=q;
while(q--) ans[++tot]=l;
}
}//简洁的贪心代码,每次都找到目前最大的那个数并按顺序存放到数组中
printf("Case #%d: ",k);
int s=1;
while(s<tot&&ans[s]==0) s++;//注意不是小于等于tot,因为最终结果必须要输出一个数,比如90+10.
for(int i=s;i<=len;i++) printf("%d",ans[i]);//如果首位是最小的就直接跳过,否则仍然以其为首位输出
printf("\n");
}
return 0;
}
中间那串贪心代码看了好久才明白,解题过程很巧妙,但是总感觉哪里不对但又举不出来反例,不过这也是贪心算法的一大特点。
再来看一道题
巴西著名城市里约热内卢举办网球锦标赛,奥斯塔普·本德尔不想错过这项赛事。将有n名玩家参加,锦标赛将遵循第一场比赛的淘汰赛规则。这意味着,如果有人输掉了一场比赛,他会立即离开比赛。
组织者仍在安排比赛网格(即游戏的顺序将发生以及谁将与谁一起玩),但他们已经确定了一条规则:只有当其中一人已经玩过的游戏数量与另一个人已经玩过的游戏数量相差不超过一个时,两个玩家才能相互对抗。当然,两位球员都必须赢得所有比赛才能继续参加比赛。
比赛还没有开始,所以观众有点无聊。Ostap决定找出锦标赛获胜者可以参加的最大游戏数量是多少(假设使用了上述规则)。但是,如果没有您的帮助,他不太可能处理这个问题。
这道题思路不是很难,冠军先打两把,让出冠军以外的人打最少的场数使有人能赢一把,再让冠军跟这个人打,再让出冠军以外的人打最少的场数使有人能赢两把,以此类推,而随着赢得场数增加,需要的最少人数是一个斐波那契数列,通过对人数的判断给出最多能赢得场数。
#include<iostream>
using namespace std;
int main()
{
int n;
int num[1000]= {0};
num[2]=2;
cin>>n;
int a[100];
a[1]=2;
a[2]=3;
for(int i=3; i<=99; i++)
{
a[i]=a[i-1]+a[i-2];
num[i]+=a[i];
}
for(int i=1; i<=99; i++)
{
if(n==2)
{
cout<<1;
break;
}
if(n>=num[i]+3&&n<num[i+1]+3)
{
cout<<i+1;
break;
}
}
}
不是很难,但是很贪
做了这些贪心题,对贪心有了更多的感悟,贪心只是一种基础算法,没有什么类型不类型的题目,只要题目条件合适,自己也能想到合理的贪心标准,就可以使用来尝试解题