2021年3月15日-3月21日ACM学习日志

本周学习:贪心算法和一些写代码的小细节

一:贪心算法

思想:局部最优达到全局最优。
做题时想法:看到一个题,我们不知道一眼就看出他是个贪心的题,我们先用自己最根本最基础的思路走一遍,然后在其中我们再思考哪里可以优化的地方,渐渐的,我们就能用到贪心的思想。
遇到题实在不会时:
第一步,我们先读懂文字版的题意。
第二步,我们看看能不能用数学推导出一个条件来。
第三步,如果不能推出一些条件,我们可以查看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,所以我也要再遇到题时列一些式子看看有没有什么新发现。接下来就是刷一些贪心的题了,渐渐熟悉吧运用自己学的一些小细节。

猜你喜欢

转载自blog.csdn.net/qq_52237775/article/details/115021652