递推算法与二分算法

递推算法:

(一)斐波那契数列

以下数列0 1 1 2 3 5 8 13 21 …被称为斐波纳契数列。

这个数列从第3项开始,每一项都等于前两项之和。

输入一个整数N,请你输出这个序列的前N项。

输入格式

一个整数N。

输出格式

在一行中输出斐波那契数列的前N项,数字之间用空格隔开。

数据范围

0<N<460<N<46

输入样例:

5

输出样例:

0 1 1 2 3

#include<iostream>
using namespace std;
int f[100];
int main()
{
    int i,j,n;
    cin>>n;
    f[1]=0,f[2]=1,f[3]=1;
    for(i=3;i<=n;i++)
        f[i]=f[i-1]+f[i-2];
    for(i=1;i<=n;i++)
        cout<<f[i]<<" ";
    return 0;
}

(二)费解的开关

你玩过“拉灯”游戏吗?25盏灯排成一个5x5的方形。每一个灯都有一个开关,游戏者可以改变它的状态。每一步,游戏者可以改变某一个灯的状态。游戏者改变一个灯的状态会产生连锁反应:和这个灯上下左右相邻的灯也要相应地改变其状态。

我们用数字“1”表示一盏开着的灯,用数字“0”表示关着的灯。下面这种状态

10111
01101
10111
10000
11011

在改变了最左上角的灯的状态后将变成:

01111
11101
10111
10000
11011

再改变它正中间的灯后状态将变成:

01111
11001
11001
10100
11011

给定一些游戏的初始状态,编写程序判断游戏者是否可能在6步以内使所有的灯都变亮。

输入格式

第一行输入正整数n,代表数据中共有n个待解决的游戏初始状态。

以下若干行数据分为n组,每组数据有5行,每行5个字符。每组数据描述了一个游戏的初始状态。各组数据间用一个空行分隔。

输出格式

一共输出n行数据,每行有一个小于等于6的整数,它表示对于输入数据中对应的游戏状态最少需要几步才能使所有灯变亮。

对于某一个游戏初始状态,若6步以内无法使所有灯变亮,则输出“-1”。

数据范围

0<n500

输入样例:

3
00111
01011
10001
11010
11100

11101
11101
11110
11111
11111

01111
11111
11111
11111
11111

输出样例:

3
2
-1

思路:我们可以来枚举第一行的操作,第一行一共有5个,所以有5个操作,然后第二行根据他这一列,第一行是“0”的进行按下开关操作,
以此类推,到最后一行时,直接判断是否全为“1”,如果全为“1”,则代表可以完成,在判断操作次数是否大于6,否则不可以完成。
代码:
#include<iostream>
#include<cstring>
using namespace std;

char g[7][7];
int dx[5]={-1,0,1,0,0},dy[5]={0,1,0,-1,0};
//判断函数
void turn(int x,int y)
{
    for(int i=0;i<5;i++)
    {
        int a=x+dx[i];
        int b=y+dy[i];
        if(a<0||a>=5||b<0||b>=5)
            continue;
        g[a][b]^=1;//异或操作,将原来是1的变为0,原来是0的变为1
    }
}

int main()
{
    int t,i,j;
    cin>>t;
    while(t--)
    {
        for(i=0;i<5;i++)
            cin>>g[i];//存入字符串
        char black[7][7];
        int ans=100000;
        for(int op=0;op<32;op++)//模拟第一行一共有2的5次方操作选择,每一位都有两种选择,故32中选择
        {
           int step=0;
            memcpy(black,g,sizeof(g));//保存一份原先的字符串数组
            for(j=0;j<5;j++)
            {
                if(op >> j & 1)//判断该位是否有1,有1则进行操作
                {
                    step++;
                    turn(0,j);
                }
            }
            //要对前4排进行判断,如果为0则后一排就操作,否则不操作
            for(i=0;i<4;i++)
            {
                for(j=0;j<5;j++)
                {
                    if(g[i][j]=='0')
                    {
                        step++;
                        turn(i+1,j);
                    }
                }
            }
            //最后看最后一排是否全为1
            bool flag=false;
            for(i=0;i<5;i++)
            {
                if(g[4][i]=='0')
                {
                    flag=true;
                    break;
                }
            }
            //更新最小步数
            if(!flag)
                ans=min(ans,step);
            //还原字符串数组
            memcpy(g,black,sizeof(black));
        }
        //如果超出6步,则更改为-1
        if(ans>6)
            ans=-1;
        cout<<ans<<endl;
    }
    return 0;
}

(三)翻硬币

小明正在玩一个“翻硬币”的游戏。

桌上放着排成一排的若干硬币。我们用 * 表示正面,用 o 表示反面(是小写字母,不是零)。

比如,可能情形是:**oo***oooo

如果同时翻转左边的两个硬币,则变为:oooo***oooo

现在小明的问题是:如果已知了初始状态和要达到的目标状态,每次只能同时翻转相邻的两个硬币,那么对特定的局面,最少要翻动多少次呢?

我们约定:把翻动相邻的两个硬币叫做一步操作。

输入格式

两行等长的字符串,分别表示初始状态和要达到的目标状态。

输出格式

一个整数,表示最小操作步数

数据范围

输入字符串的长度均不超过100。
数据保证答案一定有解。

输入样例1:

**********
o****o****

输出样例1:

5

输入样例2:

*o**o***o***
*o***o**o***

输出样例2:

1
思路:其实我们可以发现最少翻动的次数是固定的,我们的操作时从前往后进行字符串的对比,
如果有不一样的,就进行翻转,且前一个的翻转只能影响后一个的状态,这样的步数就是最小的同时也是固定的
代码:
#include<iostream>
using namespace std;
int main()
{
    string a,b;
    int i,j;
    cin>>a>>b;
    int ans=0;
    for(i=0;i<a.length();i++)
    {
        if(a[i]==b[i])
            continue;
        else
        {
            ans++;
            for(j=i;j<=i+1;j++)
            {
                if(a[j]=='*')
                    a[j]='o';
                else
                    a[j]='*';
            }
        }
    }
    cout<<ans<<endl;
    return 0;
}

(四)飞行员兄弟

“飞行员兄弟”这个游戏,需要玩家顺利的打开一个拥有16个把手的冰箱。

已知每个把手可以处于以下两种状态之一:打开或关闭。

只有当所有把手都打开时,冰箱才会打开。

把手可以表示为一个4х4的矩阵,您可以改变任何一个位置[i,j]上把手的状态。

但是,这也会使得第i行和第j列上的所有把手的状态也随着改变。

请你求出打开冰箱所需的切换把手的次数最小值是多少。

输入格式

输入一共包含四行,每行包含四个把手的初始状态。

符号“+”表示把手处于闭合状态,而符号“-”表示把手处于打开状态。

至少一个手柄的初始状态是关闭的。

输出格式

第一行输出一个整数N,表示所需的最小切换把手次数。

接下来N行描述切换顺序,每行输入两个整数,代表被切换状态的把手的行号和列号,数字之间用空格隔开。

注意:如果存在多种打开冰箱的方式,则按照优先级整体从上到下,同行从左到右打开。

数据范围

1i,j4

输入样例:

-+--
----
----
-+--

输出样例:

6
1 1
1 3
1 4
4 1
4 3
4 4
思路:对于这个4*4的矩阵,我们的操作有2次,我们可以进行暴力枚举

// 0 1 2 3
// 4 5 6 7
// 8 9 10 11
// 12 13 14 15
//对每一位操作来判断

用操作数来对数组中的每一位进行判断是否要进行该操作,

操作后记录操作的路径,进行操作时注意该操作位进行了两次转换,状态不变因此我们需要再进行一次这个位置的单独转换

最后判断是否有无“+”,然后更新最优数组。

代码:

#include<iostream>
#include<vector>
#include<cstring>
using namespace std;

#define x first   //重定义first和second为x和y
#define y second

char g[6][6],black[6][6];
//得到数组的对应值
//  0   1  2   3
//  4   5  6   7
//  8   9  10  11
//  12  13 14  15
int  get(int x,int y)
{
    return 4*x+y;
}
//转化每一位
void turn_one(int x,int y)
{
    if(g[x][y]=='-')
        g[x][y]='+';
    else
        g[x][y]='-';
}
//转换所有
void turn_all(int x,int y)
{
    for(int i=0; i<4; i++)
    {
        turn_one(x,i);
        turn_one(i,y);
    }
    turn_one(x,y);
}

int main()
{
    for(int i=0; i<4; i++)
    {
        cin>>g[i];
    }
    vector< pair<int,int> > ans;
    //一共有2的16次方个操作数,对应每一位都有2个选择,一共有16个数
    for(int op=0; op < 1<<16; op++)
    {
        vector< pair<int,int> > temp;//用来保存数据操作
        memcpy(black,g,sizeof(g));//设置一个用来还原的数组
        for(int i=0; i<4 ; i++)
        {
            for(int j=0; j<4; j++)
            {
                //  0   1  2   3
                //  4   5  6   7
                //  8   9  10  11
                //  12  13 14  15
                //对每一位操作来判断
                if(op>>get(i,j)&1)
                {
                    temp.push_back({i,j});//放到数组里
                    turn_all(i,j);//转换该点的每一行和每一列
                }
            }
        }
        //判断是否有无+号
        bool flag=false;
        for(int i=0; i<4; i++)
        {
            for(int j=0; j<4; j++)
            {
                if(g[i][j]=='+')
                {
                    flag=true;
                    break;
                }
            }
        }
        //更新最优数组
        if(!flag)
        {
            if(ans.empty()||ans.size()>temp.size())
                ans=temp;
        }
        //再将数组还原
        memcpy(g,black,sizeof(g));
    }
    cout<<ans.size()<<endl;
    for(int i=0; i<ans.size(); i++)
    {
        cout<<ans[i].x+1<<" "<<ans[i].y+1<<endl;
    }
    return 0;
}
#include<iostream>
#include<vector>
#include<cstring>
using namespace std;

#define x first   //重定义first和second为x和y
#define y second

char g[6][6],black[6][6];
//得到数组的对应值
//  0   1  2   3
//  4   5  6   7
//  8   9  10  11
//  12  13 14  15
int  get(int x,int y)
{
    return 4*x+y;
}
//转化每一位
void turn_one(int x,int y)
{
    if(g[x][y]=='-')
        g[x][y]='+';
    else
        g[x][y]='-';
}
//转换所有
void turn_all(int x,int y)
{
    for(int i=0; i<4; i++)
    {
        turn_one(x,i);
        turn_one(i,y);
    }
    turn_one(x,y);
}

int main()
{
    for(int i=0; i<4; i++)
    {
        cin>>g[i];
    }
    vector< pair<int,int> > ans;
    //一共有2的16次方个操作数,对应每一位都有2个选择,一共有16个数
    for(int op=0; op < 1<<16; op++)
    {
        vector< pair<int,int> > temp;//用来保存数据操作
        memcpy(black,g,sizeof(g));//设置一个用来还原的数组
        for(int i=0; i<4 ; i++)
        {
            for(int j=0; j<4; j++)
            {
                //  0   1  2   3
                //  4   5  6   7
                //  8   9  10  11
                //  12  13 14  15
                //对每一位操作来判断
                if(op>>get(i,j)&1)
                {
                    temp.push_back({i,j});//放到数组里
                    turn_all(i,j);//转换该点的每一行和每一列
                }
            }
        }
        //判断是否有无+号
        bool flag=false;
        for(int i=0; i<4; i++)
        {
            for(int j=0; j<4; j++)
            {
                if(g[i][j]=='+')
                {
                    flag=true;
                    break;
                }
            }
        }
        //更新最优数组
        if(!flag)
        {
            if(ans.empty()||ans.size()>temp.size())
                ans=temp;
        }
        //再将数组还原
        memcpy(g,black,sizeof(g));
    }
    cout<<ans.size()<<endl;
    for(int i=0; i<ans.size(); i++)
    {
        cout<<ans[i].x+1<<" "<<ans[i].y+1<<endl;
    }
    return 0;
}

二分算法

(一)数的范围

给定一个按照升序排列的长度为n的整数数组,以及 q 个查询。

对于每个查询,返回一个元素k的起始位置和终止位置(位置从0开始计数)。

如果数组中不存在该元素,则返回“-1 -1”。

输入格式

第一行包含整数n和q,表示数组长度和询问个数。

第二行包含n个整数(均在1~10000范围内),表示完整数组。

接下来q行,每行包含一个整数k,表示一个询问元素。

输出格式

共q行,每行包含两个整数,表示所求元素的起始位置和终止位置。

如果数组中不存在该元素,则返回“-1 -1”。

数据范围

1n100000
1q10000
1k10000

输入样例:

6 3
1 2 2 3 3 4
3
4
5

输出样例:

3 4
5 5
-1 -1
思路:找起始位置实际上就是求:f[i]>=x;而查找最终位置实际上是在求:f[i]<=x
代码:
#include<cstdio>
#include<iostream>
using namespace std;
const int N=100010;
int f[N];
int n,q;
int main()
{
    int i,j;
    cin>>n>>q;
    for(i=0;i<n;i++)
    {
        scanf("%d",&f[i]);
    }
    while(q--)
    {
        int x;
        scanf("%d",&x);
        int l=0,r=n-1;
        while(l<r)
        {
            int mid=(l+r)/2;
            if(f[mid]>=x)
                r=mid;
            else
                l=mid+1;
        }
        if(f[r]==x)
        {
            cout<<r<<" ";
            r=n-1;
            while(l<r)
            {
                int mid=(l+r+1)/2;
                if(f[mid]<=x)
                    l=mid;
                else
                    r=mid-1;
            }
            cout<<l<<endl;
        }
        else
        {
            cout<<"-1 -1"<<endl;
        }
    }
    return 0;
}

(二)数的三次方根

给定一个浮点数n,求它的三次方根。

输入格式

共一行,包含一个浮点数n。

输出格式

共一行,包含一个浮点数,表示问题的解。

注意,结果保留6位小数。

数据范围

10000n10000

输入样例:

1000.00

输出样例:

10.000000
代码:
#include<iostream>
#include<cstdio>
using namespace std;
int main()
{
    double x;
    cin>>x;
    double l=-10000,r=10000;
    while(r-l>1e-8)
    {
        double mid=(l+r)/2;
        if(mid*mid*mid>=x)
            r=mid;
        else
            l=mid;
    }
    printf("%lf",r);
    return 0;
}

(三)机器人跳跃问题

机器人正在玩一个古老的基于DOS的游戏。

游戏中有N+1座建筑——从0到N编号,从左到右排列。

编号为0的建筑高度为0个单位,编号为 i 的建筑高度为H(i)个单位。

起初,机器人在编号为0的建筑处。

每一步,它跳到下一个(右边)建筑。

假设机器人在第k个建筑,且它现在的能量值是E,下一步它将跳到第k+1个建筑。

如果H(k+1)>E,那么机器人就失去H(k+1)-E的能量值,否则它将得到E-H(k+1)的能量值。

游戏目标是到达第N个建筑,在这个过程中能量值不能为负数个单位。

现在的问题是机器人以多少能量值开始游戏,才可以保证成功完成游戏?

输入格式

第一行输入整数N。

第二行是N个空格分隔的整数,H(1),H(2),…,H(N)代表建筑物的高度。

输出格式

输出一个整数,表示所需的最少单位的初始能量值。

数据范围

1N,H(i)105

输入样例1:

5
3 4 3 2 4

输出样例1:

4

输入样例2:

3
4 4 4

输出样例2:

4

输入样例3:

3
1 6 4

输出样例3:

3
思路:这里要注意的是x可能会爆,因此当x大于最大值时,必然是成立的,我们就可以返回true
代码:
#include<iostream>
using namespace std;
typedef long long ll;
ll n,h[100010],i,j,maxn=-1;

bool check(ll x)
{
    bool flag=true;
    for(ll i=0;i<n;i++)
    {
        if(h[i]>x)
            x=x-(h[i]-x);
        else
            x=x+(x-h[i]);
        if(x<0)
        {
            flag=false;
            break;
        }
        if(x>maxn)
            break;
    }
    return flag;
}
int main()
{
    cin>>n;
    for(i=0;i<n;i++)
    {
        cin>>h[i];
        maxn=max(maxn,h[i]);
    }
    ll l=0,r=100000;
    while(l<r)
    {
        ll mid=l+(r-l)/2;
        if(check(mid))
            r=mid;
        else
            l=mid+1;
    }
    cout<<r<<endl;
    return 0;
}

(四)分巧克力

儿童节那天有 K 位小朋友到小明家做客。

小明拿出了珍藏的巧克力招待小朋友们。

小明一共有 N 块巧克力,其中第 i 块是 Hi×Wi 的方格组成的长方形。

为了公平起见,小明需要从这 N 块巧克力中切出 K 块巧克力分给小朋友们。

切出的巧克力需要满足:

  1. 形状是正方形,边长是整数
  2. 大小相同

例如一块 6×5的巧克力可以切出 6 块 2×2 的巧克力或者 2 块 3×3 的巧克力。

当然小朋友们都希望得到的巧克力尽可能大,你能帮小明计算出最大的边长是多少么?

输入格式

第一行包含两个整数 N 和 K

以下 N 行每行包含两个整数 Hi 和 Wi

输入保证每位小朋友至少能获得一块 1×11×1 的巧克力。

输出格式

输出切出的正方形巧克力最大可能的边长。

数据范围

1N,K105
1Hi,Wi105

输入样例:

2 10
6 5
5 6

输出样例:

2
思路:注意这里不可以用面积来进行判断,而应该分别用长和宽来分别除以边长,然后相乘
代码:
#include<iostream>
using namespace std;
int n,k,s[100010];
int h[100010],w[100010];
bool check(int x)
{
    int ans=0;
    for(int i=0;i<n;i++)
    {
        ans+=(h[i]/x)*(w[i]/x);//来计算可以分给多少人
    }
    if(ans>=k)
        return true;
    else
        return false;
}
int main()
{
    int i,j,maxn=-1;
    cin>>n>>k;
    for(i=0;i<n;i++)
    {
        cin>>h[i]>>w[i];
    }
    int l=0,r=100010;
    while(l<r)
    {
        int mid=(l+r+1)/2;//加1操作,避免死循环
        if(check(mid))
            l=mid;
        else
            r=mid-1;
    }
    cout<<l<<endl;
    return 0;
}

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

收获:对二分的两种方法的掌握理解的更加渗透,理解二分的时候什么时候需要+1,避免死循环;同时掌握了一些递推的思路和寻求方法

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

蓝桥杯第二次训练

2019.12.10

xlf

 
12-11 17:17