递归专项

本文介绍了几种使用递归实现的枚举算法,包括指数型、组合型和排列型枚举,以及解决具体问题如费解的开关游戏、奇怪的汉诺塔等。文章通过实例详细解释了递归算法的设计思路和代码实现。

递归实现指数型枚举

从 1~n 这 n 个整数中随机选取任意多个,输出所有可能的选择方案。

输入格式

输入一个整数n。

输出格式

每行输出一种方案。

同一行内的数必须升序排列,相邻两个数用恰好1个空格隔开。

对于没有选任何数的方案,输出空行。

本题有自定义校验器(SPJ),各行(不同方案)之间的顺序任意。

代码:

#include<iostream>

using namespace std;

int n;

void dfs(int u,int state)
{
    if(u==n)
    {
        for(int i=0;i<n;i++)
            if(state>>i&1)
                cout<<i+1<<' ';
        cout<<endl;
        return;
    }

    dfs(u+1,state);
    dfs(u+1,state|1<<u);
}

int main()
{
    cin>>n;
    dfs(0,0);
    return 0;
}

递归实现组合型枚举

从 1~n 这 n 个整数中随机选出 m 个,输出所有可能的选择方案。

输入格式

两个整数n,m 在同一行用空格隔开。

输出格式

按照从小到大的顺序输出所有方案,每行1个。

首先,同一行内的数升序排列,相邻两个数用一个空格隔开。

其次,对于两个不同的行,对应下标的数一一比较,字典序较小的排在前面(例如1 3 5 7排在1 3 6 8前面)。

代码:

#include<iostream>

using namespace std;

int n,m;

void dfs(int u,int sum,int state)
{
    if(sum+n-u<m)return;

    if(sum==m)
    {
        for(int i=0;i<n;i++)
            if(state>>i&1)
                cout<<i+1<<' ';
        cout<<endl;
        return;
    }

    //if(u==n)return; 第一个if会直接判断出去所以不需要写这句话


    dfs(u+1,sum+1,state|1<<u);
    dfs(u+1,sum,state);
}

int main()
{
    cin>>n>>m;
    dfs(0,0,0);
    return 0;
}

递归实现排列型枚举

把 1~n 这 n 个整数排成一行后随机打乱顺序,输出所有可能的次序。

输入格式

一个整数n。

输出格式

按照从小到大的顺序输出所有方案,每行1个。

首先,同一行相邻两个数用一个空格隔开。

其次,对于两个不同的行,对应下标的数一一比较,字典序较小的排在前面。

代码:

//n个坑,枚举哪个数放到第一个坑,然后枚举下一个坑,枚举没有放过的
#include<iostream>
#include<vector>

using namespace std;

int n;
vector<int>path;

void dfs(int u,int state)
{
    if(u==n)
    {
        for(auto x:path)cout<<x<<' ';
        cout<<endl;
        return;
    }

    for(int i=0;i<n;i++)
    {
        if(!(state>>i&1))
        {
            path.push_back(i+1);
            dfs(u+1,state|1<<i);
            path.pop_back();//枚举完当前情况后,恢复现场。保证同一次枚举的时候,对于所有的分支在进入之前他的所有状态都是一样的。
        }
    }
}

int main()
{
    cin>>n;
    dfs(0,0);
    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”。

代码:

#include<iostream>
#include<cstring>//memcpy在这儿

using namespace std;

char g[10][10];
const int INF=1e5;
int dx[5]={0,-1,0,1,0},dy[5]={0,0,1,0,-1};//向量技巧,以此表示中心,向上,向右,向下,向左

void turn(int x,int y)//按下操作
{
    for(int i=0;i<5;i++)//一开始写错了写了两重循环
    {
        int a=x+dx[i],b=y+dy[i];
        if(a>=0&&a<5&&b>=0&&b<5)
        {
            g[a][b]^=1;
        }
    }
}

int work()
{
    int ans=INF;
    for(int k=0;k<1<<5;k++)//枚举第一行的情况
    {
        int res=0;
        char backup[10][10];
        memcpy(backup,g,sizeof g);//记录原始状态

        for(int j=0;j<5;j++)//操作第一行
        {
            if(k>>j&1)
            {
                res++;
                turn(0,j);
            }
        }

        for(int i=0;i<4;i++)//操作后面几行
        {
            for(int j=0;j<5;j++)
            {
                if(g[i][j]=='0')
                {
                    res++;
                    turn(i+1,j);
                }
            }
        }

        bool is_successful=true;//根据最后一行有无0判断是否成功
        for(int i=0;i<5;i++)
        {
            if(g[4][i]=='0')
            {
                is_successful=false;
                break;
            }
        }

        if(is_successful)
        {
            ans=min(res,ans);//记录最小值
        }

        memcpy(g,backup,sizeof g);//返回初始状态
    }

    if(ans>6) return -1;//return 的位置写错了
    else return ans;
}

int main()
{
    int n;
    cin>>n;
    while(n--)
    {
        for(int i=0;i<5;i++)cin>>g[i];//char数组不需要写到二维
        cout<<work()<<endl;
    }
}

奇怪的汉诺塔

汉诺塔问题,条件如下:

1、这里有A、B、C和D四座塔。

2、这里有n个圆盘,n的数量是恒定的。

3、每个圆盘的尺寸都不相同。

4、所有的圆盘在开始时都堆叠在塔A上,且圆盘尺寸从塔顶到塔底逐渐增大。

5、我们需要将所有的圆盘都从塔A转移到塔D上。

6、每次可以移动一个圆盘,当塔为空塔或者塔顶圆盘尺寸大于被移动圆盘时,可将圆盘移至这座塔上。

请你求出将所有圆盘从塔A移动到塔D,所需的最小移动次数是多少。

代码:

#include<iostream>
#include<cstring>

using namespace std;

typedef long long ll;

int main()
{
    int d[15],f[15];
    d[1]=1;
    for(int i=2;i<13;i++)
    {
        d[i]=2*d[i-1]+1;
    }

    memset(f,0x3f,sizeof f);//使f中最大,后面用来min
    f[0]=0;
    for(int i=1;i<13;i++)
    {
        for(int j=0;j<i;j++)
        {
            f[i]=min(f[i],f[j]*2+d[i-j]);//后面的算法借鉴基本汉诺塔的算法,因为共四个柱子,最开始把一定数量的圆盘移动到一个柱子上,此时还剩两个柱子,可以完成基础汉诺塔的操作。
        }
    }

    for(int i=1;i<=12;i++)cout<<f[i]<<endl;
    return 0;
}

约数之和

假设现在有两个自然数A和B,S是A^B的所有约数之和。

请你求出S mod 9901的值是多少。

输入格式

在一行中输入用空格隔开的两个整数A和B。

输出格式

输出一个整数,代表S mod 9901的值。

分析:下图为需要用到的公式。

数学问题相关

代码:

#include<iostream>

using namespace std;

const int mod=9901;

int quickpower(int a,int k)
{
    a%=mod;
    int res=1;
    while(k)
    {
        if(k&1)res=res*a%mod;
        a=a*a%mod;
        k>>=1;
    }
    return res;
}

int sum(int p,int k)//这里是难点
{
    if(k==0)return 1;
    if(k%2==0)return (p%mod*sum(p,k-1)%mod+1)%mod;
    return (quickpower(p,k/2+1)+1)%mod*sum(p,k/2)%mod;//这里不取余会有错误?错误数据50000000 50000000答案5531
    
}

int main()
{
    int a,b;
    cin>>a>>b;
    
    int ans=1;
    for(int i=2;i<=a;i++)
    {
        int k=0;    
        while(a%i==0)//错写成a%i!=0
        {
            a/=i;
            k++;
        }
        if(k)ans=ans*sum(i,k*b)%mod;
    }
    
    if(a==0)ans=0;
    cout<<ans<<endl;
    return 0;
}

分形之城

城市的规划在城市建设中是个大问题。

不幸的是,很多城市在开始建设的时候并没有很好的规划,城市规模扩大之后规划不合理的问题就开始显现。

而这座名为 Fractal 的城市设想了这样的一个规划方案,如下图所示:

city.png

当城区规模扩大之后,Fractal 的解决方案是把和原来城区结构一样的区域按照图中的方式建设在城市周围,提升城市的等级。

对于任意等级的城市,我们把正方形街区从左上角开始按照道路标号。

虽然这个方案很烂,Fractal 规划部门的人员还是想知道,如果城市发展到了等级 N,编号为 A 和 B 的两个街区的直线距离是多少。

街区的距离指的是街区的中心点之间的距离,每个街区都是边长为 10 米的正方形。

输入格式

第一行输入正整数n,表示测试数据的数目。

以下n行,输入n组测试数据,每组一行。

每组数据包括三个整数 N,A,B, 表示城市等级以及两个街区的编号,整数之间用空格隔开。

输出格式

一共输出n行数据,每行对应一组测试数据的输出结果,结果四舍五入到整数。

难点在于坐标转化。

代码:

#include<iostream>
#include<cmath>

using namespace std;

typedef long long LL;
typedef pair<LL,LL> PLL;

PLL calc(LL n,LL m)
{
    if(n==0)return{0,0};
    LL len=1ll<<n-1,cnt=1ll<<2*n-2;
    auto pos=calc(n-1,m%cnt);
    auto x=pos.first,y=pos.second;
    auto z=m/cnt;
    if(z==0) return {y,x};
    if(z==1) return {x,y+len};
    if(z==2) return {x+len,y+len};
    return {2*len-1-y,len-1-x};//迷之减一,通过画图发现上面的+len确实比这里需要平移的距离长一小节
}

int main()
{
    int t;
    cin>>t;
    while(t--)
    {
        LL n,a,b;
        cin>>n>>a>>b;
        auto ac=calc(n,a-1);
        auto bc=calc(n,b-1);
        double x=ac.first-bc.first,y=ac.second-bc.second;
        printf("%.0lf\n",sqrt(x*x+y*y)*10);//四舍五入保留整数.0lf,sqrt里多写了个*号导致没过
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

肥羊也

感谢给肥羊投喂!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值