专题整理:基础算法(1)

本博客包含:贪心、搜索(dfs、bfs)、二分

一、贪心

1-1、题目链接:https://www.luogu.com.cn/problem/P1007

1-2、题目简述:

在一座桥上有一群不知道行进方向的士兵,他们每秒行动一个单位,相互碰到之后会掉头行进(掉头不消耗时间),求解所有士兵走下桥的最长用时和最短用时是多少。

1-3、概念引入&&思路:

贪心,他就像自己的名字一样,简单易懂,童叟无欺。当你第一次看到这个算法的名字所想到的东西,就是它要做的东西。不像某些算法,看了名字不知道要干什么,看了讲解还是不知道要干什么。

所谓贪心,就是通过不断选择局部最优解以得到总体最优解的一种算法。

我们来看一下这题就明白了。

乍一看,要考虑掉头好想十分复杂,但是我们要明白一点,在这题当中人和人是平等的,也就是说两个人相互掉头实际上和他们相互穿过彼此的身体没有什么区别

说到这里有些同学心中已经有答案了,将每一个士兵到两岸的距离和最大值最小值进行比较更新答案就行了。

那么恭喜你,你已经掌握贪心了。通过不断选取每个士兵的极值(局部最优解)得到最终的最大值最小值(整体最优解),这就是贪心算法。

顺便一提,由于裸的贪心题实在是太简单了,所以贪心算法往往会和其他的算法一起考察,或者将贪心思想隐藏在具有迷惑性的外表下。

总之,贪心这种东西,想到就是赚到。

1-4、代码:

#include <iostream>
#include<cstdio>
#include<algorithm>
#include <cstring>
#include<cmath> 
#include <stack>
#define N 500050
using namespace std;
int maxn,minn;
void read(int & p)  
{  
    p=0;
    char c=getchar();  
    while(c<'0' or c>'9') c=getchar();  
    while(c>='0' and c<='9')  
        p=p*10+(c-'0'),c=getchar();  
}  

int main()
{
    int r,n;
    read(r);read(n);
    for(int i=1;i<=n;i++)
    {
        int k;
        read(k);
        maxn=max(k,max(r-k+1,maxn));
        minn=max(min(k,r-k+1),minn);
        }
        printf("%d ",minn);
        printf("%d",maxn);
        return 0;
}

二、二分---从二分查找到二分答案

2-1、二分查找

2-1-1、题目链接:https://www.luogu.com.cn/problem/P2759

2-1-2、题目简述:

使得xx达到或超过n(n<=2000000000)位数字的最小正整数x是多少?

2-1-3、概念引入&&思路:

二分法,可以简单地分为二分查找和二分答案,而二分答案实际上就是二分查找法的延伸。

一个数字的位数,我们可以用取对数来求取,实际上就是lg(x)+1那么我们可以对xx取对数。

即使如此,n的大小也不允许我们进行爆搜,爆搜必定超时。

既然如此,我们就不断取中间的值,将区间一分为二,将其和答案进行比较,若小了的话,往右区间查找,大了的话往左区间继续查找。

这就是二分查找,是不是想起了一个古老的游戏?猜数字?在1~100内猜一个数字,效率最高的依旧是二分查找,它的复杂度是O(logN)的

但是它有一个限制:查找的序列必须采用顺序存储结构并且有序排列,这就是一个很大的弱点。

而它的孪生兄弟二分答案,就有着巨大的作用了。

2-1-4、代码:

 

#include <iostream>
#include<cstdio>
#include<algorithm>
#include <cstring>
#include<cmath> 
#include <stack>
#define N 500050
#define ll long long
using namespace std;
void read(long long & p)  
{  
    p=0;
    char c=getchar();  
    while(c<'0' or c>'9') c=getchar();  
    while(c>='0' and c<='9')  
        p=p*10+(c-'0'),c=getchar();  
}  
int main()
{
ll t;
ll ans=0;
read(t);
ll l=1,r=99999999999999999;
while(l<=r)
{
    ll mid=(l+r)>>1;
    ll num=mid*log10(mid)+1;
    if(t<=num) 
    {
        ans=mid;
        r=mid-1;
        }
    else  l=mid+1;
}
printf("%lld",ans);
return 0;
}

 

2-2、二分答案

2-2-1、题目链接:https://www.luogu.com.cn/problem/P1577

2-2-2、题目简述:

N条绳子,它们的长度分别为Li如果从它们中切割出K条长度相同的绳子这K 条绳子每条最长能有多长?答案保留到小数点后2位(直接舍掉2位后的小数)。

2-2-3、概念引入&&思路:

二分答案比起二分查找,实际上就多了一个check函数,而且这个函数是精髓,本质上来说就是通过二分法来枚举出答案,然后再验证你的答案是否正确,实际上就是二分查找+验证

但是二分答案有一些明确的特征一是答案具有单调性,二是经常用来寻找最小值最大或者最大值最小的问题。

我们回到题目中来,因为所有的数字保留两位,我们干脆将其扩大100倍变成整数,这样子方便计算。

我们用二分法枚举答案,然后看我们枚举的长度能否满足K条的要求,满足,扩大答案值,不满足,缩小答案值,直到我们的左右区间不能再二分的时候,那就是最佳答案了。

2-2-4、题解:

 

#include<iostream> 
#include<cstdio>
#include<string>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<stack>
#include<algorithm>
#include<queue>
#define MAXN 100010
using namespace std;
int N,K;
double L[MAXN];
int l[MAXN];

inline void read(int &p)
{    
    p=0;
    int f=1;char ch=getchar();
    while(ch<'0'||ch>'9') {if(ch=='-') f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9') p=p*10+(ch-'0'),ch=getchar();
    p*=f;
}

inline bool judge(int len)
{
    int num=0;
    for(int i=1;i<=N;i++) num+=l[i]/len;
    return num>=K;
}

int main()
{
    read(N);read(K);
    int INF=1e9+7;
    for(int i=1;i<=N;i++)
    {
        scanf("%lf",&L[i]);
        l[i]=int(L[i]*100);
    }
    int l=0,r=INF;
    while(l<=r)
    {
        int mid=(l+r)/2;
        if(mid==0) break;
        if(judge(mid)) l=mid+1;
        else r=mid-1;
    }
    printf("%.2f",r/100.0);
    return 0;
}

 

三、搜索

有个Dalao曾经跟我说过这句话:“能用枚举或者暴搜得到答案的题,即使相比正解T了,这也一定是道水题。”

我回去对照了自己刷的题之后,确认了自己是个five的事实,我似乎没有见过那种题目。

 

什么是搜索?
这又是一个把内容写在名字上的算法。所谓搜索,就是将所有可能性都考虑到,并且从中找到符合条件的解的算法。即使是搜索,也分为BFS(广度优先搜索)DFS(深度优先搜索)两种

3-1、DFS(深度优先搜素)

3-1-1、题目链接:https://vjudge.net/contest/422351#problem/C

3-1-2、题目简介:

实际上这题我曾经写过一篇题解,这是一道背包问题,同时由于数据较小,可以使用搜索的办法求解。

这里我们可以拿出来讲解DFS。

3-1-3、思路&&概念引入:

由于我的题解总是喜欢写一题多解的题目,或者难解的题目,所以这其中基本上绕不开暴力算法,这是形成思路基础,暴力基本上是最简单的解法。

什么是DFS(深度优先搜索)?
所谓深度优先,就是撞死南墙不回头,我们从第一个开始,不断找下一个情况,直到找到答案或者超出限制了。

当超出限制之后,我们使用递归对当前解进行递归回溯,回到上一个状态再向下寻找。

以这题为例,每个物品都有选择和不选两个决策,那么我们依照深度优先就是全部都选择直到物品都没了,这个时候在进行判断,发现不符合于是回溯到上个东西还没选择的时候,对这个东西作出不选的决策。

什么是剪枝?

在搜索的时候我们发现两个问题,一是如果当前的重量超过了承受量,二是选择的数量到达了限制,这两种情况下一定不会是最优解,那么我们就进行剪枝,直接回溯到上一层,不用再向下搜索了,这就是剪枝操作。

3-1-4、代码:

inline void dfs(int nlim,int num,int w,int p)
{
    if(w>W) return ;
    if(num>k) return ; 
    if(nlim==n)
    {
        if(p>pmax)pmax=p;
        return ;
    }
    dfs(nlim+1,num+1,w+b[nlim+1],p+a[nlim+1]);//选择下一个
    dfs(nlim+1,num,w,p);//不选择下一个 
}

3-2、BFS(广度优先搜索)

3-2-1、题目链接:https://www.luogu.com.cn/problem/P1949

3-2-2、题目简介:

3-2-3、思路&&概念引入:

什么是BFS(广度优先搜索)?

我们知道DFS的特性:撞死南墙不回头,一路搜到底然后再回溯。相比之下,BFS就要聪明一些,它是利用队列保存当前搜索状态以达到一层一层搜索到结果的一种算法。

最值得讨论的是如何保存搜索状态。一般来说可以使用结构体将搜索到的东西保存下来,也可以使用hash的方法将状态进行保存。

这题我们使用hash的方法对当前状态进行保存。

从初始的状态开始用一个二维的vis数组保存当前数字和光标停留的位置,然后进行BFS。

3-2-4、代码:

#include<iostream> 
#include<cstdio>
#include<string>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<stack>
#include<algorithm>
#include<queue>#define N 1000000using namespace std;int n,t,m;
struct node{
    int num;int gb;int dep;
}u,v;
queue<node>q1;bool vis[N][7];   //数字,光标 int sig[8]={0,100000,10000,1000,100,10,1};//快读 
inline void read(int &p)
{
    p=0;
    char ch=getchar();
    while(ch<'0'||ch>'9') ch=getchar();
    while(ch>='0'&&ch<='9') p=(p<<3)+(p<<1)+(ch-'0'),ch=getchar();
}


int main()
{
    int mission,numberposnow,numberposfor,numberposfor1,numgb;
    read(u.num);read(mission);
    if(mission==u.num)
    {
        printf("0");
        return 0;
    }
    u.gb=1;u.dep=0;q1.push(u);
    while(!q1.empty())
    {
        u=q1.front();q1.pop();
        numberposnow=(u.num/sig[u.gb])%10;
        
        if(u.gb!=1)
        {
            //光标左移
            v=u;
            v.dep++;v.gb=u.gb-1;
            if(!vis[v.num][v.gb])
            {
                vis[v.num][v.gb]=true;
                q1.push(v);
            }
            //交换左端
            v=u;v.dep++;
            numberposfor=v.num/sig[1];
            v.num=v.num-numberposfor*sig[1]+numberposnow*sig[1]-numberposnow*sig[v.gb]+numberposfor*sig[v.gb];
            if(v.num==mission)
            {
                printf("%d",v.dep);
                return 0;
            }
            if(!vis[v.num][v.gb])
            {
                vis[v.num][v.gb]=true;
                q1.push(v);
            }
        }
        if(u.gb!=6)
        {
            v=u;
            //光标右移
            v.dep++;v.gb=v.gb+1;
            if(!vis[v.num][v.gb])
            {
                vis[v.num][v.gb]=true;
                q1.push(v);
             } 
             //光标交换
             v=u;v.dep++;
            numberposfor1=v.num%10;
            v.num=v.num-numberposnow*sig[v.gb]+numberposfor1*sig[v.gb]-numberposfor1+numberposnow;
            if(v.num==mission)
            {
                printf("%d",v.dep);
                return 0;
            }
        }
        if(!vis[v.num][v.gb]){vis[v.num][v.gb]=1;q1.push(v);}
        if(numberposnow!=0)
        {
            v=u;
            v.dep++;
            v.num=v.num-sig[v.gb];
            if(v.num==mission)
            {
                printf("%d",v.dep);
                return 0;
            }
            if(!vis[v.num][v.gb])
            {
                vis[v.num][v.gb]=true;
                q1.push(v);
            }
        }
        if(numberposnow!=9)
        {
            v=u;
            v.dep++;
            v.num=v.num+sig[v.gb];
            if(v.num==mission)
            {
                printf("%d",v.dep);
                return 0;
            }
            if(!vis[v.num][v.gb])
            {
                vis[v.num][v.gb]=true;
                q1.push(v);
            }
        }
        
    }
    return 0;
}

 

posted @ 2021-03-25 22:01  ztlsw  阅读(34)  评论(0编辑  收藏  举报