本周学习:贪心算法和一些写代码的小细节
一:贪心算法
思想:局部最优达到全局最优。
做题时想法:看到一个题,我们不知道一眼就看出他是个贪心的题,我们先用自己最根本最基础的思路走一遍,然后在其中我们再思考哪里可以优化的地方,渐渐的,我们就能用到贪心的思想。
遇到题实在不会时:
第一步,我们先读懂文字版的题意。
第二步,我们看看能不能用数学推导出一个条件来。
第三步,如果不能推出一些条件,我们可以查看csdn上的经典代码的实现,按他们的思路走一遍,这样我们会更加理解这个题的算法思想,然后我们回到第一步,不断加深对算法和题的理解。
第四步,在我们以后做题时看看能不能把这个题的算法思想放入别的题中,比如这一周刚用的优先队列,STL和贪心的一些小细节。
最后一步,我们看看做题时能不能适当改变一下这个题的思想,让这个题更加优化。
1:
思路过程:一开始想的太简单了,想着一共m*n张牌,我只要是m的倍数我就能稳赢一局,写完代码后,我发现结果不对,想了想,对手也不会每次只出一个小区间的数,只要他们有比我大的数,我就输了,然后我又想,对手这个m-1个人只要一个人的牌有比我大的我就输,所以只有一种情况我会赢,就是我出的牌比他们都大。我从最大的牌开始遍历,如果我的牌比他们的牌都大,我就赢一局,如果他们的牌大,那他们就有一次赢我一次的机会。
实现代码一:
#include <iostream>
#include <algorithm>
#include <cmath>
#include <iomanip>
#include <cstring>
#include <queue>
#include <stack>
const int N=1e5;
using namespace std;
bool cmp(int a,int b)
{
return a>b;
}
int k=0,m,n,a[N],sum=0,cnt=0;
bool b[N];
int main()
{
while(cin>>m>>n&&m!=0)
{
sum=0;//对手比我牌大的个数
cnt=0;//我赢的次数
memset(b,0,sizeof(b));
for(int i=0;i<n;i++)
{
cin>>a[i];
b[a[i]]=1;//我的牌是1,对手的牌是0
}
for(int i=m*n;i>0;i--)
{
if(b[i])
{
if(sum==0)//如果对手的牌中没有大时
cnt++;
else
sum--;
}
else
sum++;
}
printf("Case %d: %d\n", ++k, cnt);
}
return 0;
实现代码二:
思想:我最少赢几次就是我最多输几次,升序便利,如果对手的最小的牌比我最小的牌大,那么我必输一局。然后再用n减去这个次数,就是我赢的次数。
#include <iostream>
#include <algorithm>
#include <cmath>
#include <iomanip>
#include <cstring>
#include <queue>
#include <stack>
#include <cstdio>
const int maxn=50005;
using namespace std;
int m,n,k,sum,cnt,t=0,d;
int main()
{
int a[maxn];
bool used[maxn],vis[maxn];
while(cin>>m>>n&&m+n)
{
int ant=0;
memset(used,false,sizeof(used));
memset(vis,false,sizeof(vis));
for(int i=0;i<n;i++)
{
cin>>a[i];
vis[a[i]]=true;
}
sort(a,a+n);//升序排序
for(int i=1;i<m*n;i++)
{
if(!vis[i])
for(int j=0;j<n;j++)
if(!used[j])
{
if(a[j]<i)
{
used[j]=true;
ant++;
break;//这一步很重要,避免多次替换
}
}
}
printf("Case%d:%d\n",++t,n-ant);
}
return 0;
}
2:
给出n个物体的质量,(假设两个物体的质量为m1,m2)相互碰撞成一个物体,质量为2sqrt(m1m2),只存在两个物体相撞,求最后最少质量。
思想:我们怎么撞才会使总质量最小呢,当时我只是猜测使质量大的两个相撞,后来老师用数学公式推导出了这个想法的正确性,也学习到我们可以利用题中的条件,将条件转化成数学式子,总而发现一些规律。
公式证明如下:
设三个物体质量(a>b>c)
若质量大的先碰:ans=sqrt(sqrt(2ab)2c)
若质量小的先碰,ans=sqrt(sqrt(2bc)2a)
这里我们可以看出,若质量的大先碰,那质量大的开的开方次数更多,总质量也会更小,所以先用质量大的相碰。
但是接下来我有疑惑了,我不知道碰完之后的质量在所有物体中所在的位置,物体的数量也减少了一个,我也不能每次碰撞完就sort一次呀,所有我就想到了优先队列,用了一次后发现,真好用啊,帮我解决了很多问题。我将所有的物体放入优先队列,依次取出两个最大质量的物体,碰撞后再纳入优先队列,反复直到最后还有一个物体。
实现代码
#include <iostream>
#include <algorithm>
#include <cmath>
#include <iomanip>
#include <cstring>
#include <queue>
#include <stack>
const int maxn=110;
struct Node
{
double val;
bool operator < (const Node &a)const
{
return val<a.val;
}
};
int n;
priority_queue < Node > q;
int main()
{
while(scanf("%d",&n)!=EOF)
{
Node node;
for(int i=0;i<n;i++)
{
double val;
scanf("%lf",&val);
node.val=val;
q.push(node);
}
for(int i=1;i<n;i++)
{
double a=q.top().val;
q.pop();
double b=q.top().val;
q.pop();
node.val=2*sqrt(a*b);
q.push(node);
}
printf("%.3f\n",q.top().val);
q.pop();
}
return 0;
}
思路过程:截至时间靠前的我们要快点做,扣分多的我们也要快点做,一开始想着每天做一个,我们要先把当天是截至时间的扣分最多的先做完,而我却忘了,如果后面有必扣分的情况(截至时间是一个,而我只能做一个)而哪些必扣分的科目比我前面做完的扣分还多,那我就把前面想做完的科目换成这个扣分更多的,这样我们就会得到扣分最小总数。
实现代码
#include <iostream>
#include <algorithm>
#include <cmath>
#include <iomanip>
#include <cstring>
#include <queue>
#include <stack>
#include <cstdio>
const int N=50005;
using namespace std;
struct node
{
int x,y;
bool v;
} a[N];
int m,n,k,sum,cnt;
bool cmp (node a,node b)
{
if(a.x!=b.x)
return a.x<b.x;
return a.y>b.y;
}
int main()
{
cin>>m;
while(m--)
{
sum=0,k=1;
cin>>n;
for(int i=0; i<n; i++)
{
cin>>a[i].x;
a[i].v=false;
}
for(int i=0; i<n; i++)
cin>>a[i].y;
sort(a,a+n,cmp);
for(int i=0; i<n; i++)
{
if(a[i].x>=k)
{
k++;
continue;
}
int t=i,temp=a[i].y;
for(int j=0; j<i; j++)
{
if(a[j].y<temp&&(!a[j].v))
{
t=j;
temp=a[j].y;
}
}
sum+=temp;
a[t].v=true;
}
cout<<sum<<endl;
}
return 0;
}
二:写代码的一些小细节
1:懂得使用bool类型的变量来作为题中的一些条件
2:如果题中说有多种案例,但不知道是几种,可以用以下两种方式:
(1):while(scanf("%d,&n)!=EOF)
(2):while(cin>>n)
3:题中如果有除法运算,尽量转换为乘法等
4:优先队列中的一些问题:
//升序队列
priority_queue <int,vector<int>,greater<int> > q;
//降序队列
priority_queue <int,vector<int>,less<int> >q;
5:懂得列数学式子来使自己更加题意和得到一些很有用的条件式子
6:在确保AC的情况下,尽可能优化代码
三:本周学习感受
在做贪心算法的题目时感受到了压力,别人做的好快,我可能有的题连题意都看不懂,刚开始做那些题时,前几个都还不能充分理解贪心的意思,所以做一个题就很慢,有时候一个题我理解起来都需要很长时间,结果我写完代码,还不能AC,然后就查csdn的题解嘛,发现我写的很朴素,就是最基础的解法,可能一些特殊情况我的解法不能实现,所以我渐渐使用一些算法的知识,这样,我才慢慢做起来,也没有刚做的的那个无助了,这周最重要的是懂得,在遇到一个题时,我应该怎么做,在不会的情况下,我应该如果研究下去。说实话,算法的题真的很难,有时候我明明在本地运行上数据正确了,但提交上去就是wrong answer,可能我的数据精度不高,也可能我的代码只能解决一些问题,特殊情况的我不能解决,所以,以后写代码要先考虑所有的可能,再去写代码。还有,我自己用最朴素的方法去写代码还不如一个公式直接AC,所以我也要再遇到题时列一些式子看看有没有什么新发现。接下来就是刷一些贪心的题了,渐渐熟悉吧运用自己学的一些小细节。