递归与递推

递归

递归实现指数型枚举

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

输入格式

输入一个整数 n。

输出格式

每行输出一种方案。

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

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

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

数据范围

1≤n≤15

输入样例:

3

输出样例: 


3
2
2 3
1
1 3
1 2
1 2 3
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>

using namespace std;

const int N=16;

int n;
int st[N];

void dfs(int u)
{
   if(u>n)
   {
       for(int i=1;i<=n;i++)
           if(st[i]==1)
            printf("%d ",i);
        printf("\n");
        return;
   }
   
   st[u]=2;
   dfs(u+1);
   st[u]=0;
   
   st[u]=1;
   dfs(u+1);
   st[u]=0;
   
}

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

 

递推实现排序性枚举

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

输入格式

一个整数 n。

输出格式

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

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

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

数据范围

1≤n≤9

输入样例:

3

输出样例:

1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>

using namespace std;

const int N=10;

int n;
int states[N];
bool used[N];

void dfs(int u)
{
    if(u>n)
    {
        for(int i=1;i<=n;i++)
            printf("%d ",states[i]);
        printf("\n");
        return;
    }
    
    for(int i=1;i<=n;i++)
        if(!used[i])
        {
            states[u]=i;
            used[i]=true;
            dfs(u+1);
           
            states[u]=0;//恢复现场
            used[i]=false;
        }
}

int main()
{
    scanf("%d",&n);
    dfs(1);
    return 0;
}

 

递归实现组合型枚举

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

输入格式

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

输出格式

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

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

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

数据范围

n>0 ,
0≤m≤n
n+(n−m)≤25

输入样例:

5 3

输出样例:

1 2 3 
1 2 4 
1 2 5 
1 3 4 
1 3 5 
1 4 5 
2 3 4 
2 3 5 
2 4 5 
3 4 5 

 在排序的前提下进行裁枝

规定:后一个数大于前一个数字,就把重复的数字去除了

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>

using namespace std;


int n;
int m;

int states[100];
bool used[100];
void dfs(int u)
{
    
    if(u>m)
    {
        for(int i=1;i<=m;i++)
         printf("%d ",states[i]);
         printf("\n");
         return;
    }
    
    for(int i=1;i<=n;i++)
        if(!used[i]&&i>states[u-1])
        {
        states[u]=i;
        used[i]=true;
        dfs(u+1);
        
        states[u]=0;
        used[i]=false;
        }
}

int main()
{
    scanf("%d%d",&n,&m);
    
    
    dfs(1);
    return 0;
}

带分数

100100 可以表示为带分数的形式:100=3+69258714100=3+69258714

还可以表示为:100=82+3546197100=82+3546197

注意特征:带分数中,数字 1∼91∼9 分别出现且只出现一次(不包含 00)。

类似这样的带分数,100100 有 1111 种表示法。

输入格式

一个正整数。

输出格式

输出输入数字用数码 1∼91∼9 不重复不遗漏地组成带分数表示的全部种数。

数据范围

1≤N<106

输入样例1:

100

输出样例1:

11

输入样例2:

105

输出样例2:

6

暴力解法:

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;

const int N=10;
int ans,n;
int u;
int num[N];
bool used[N];

int calc(int l,int r)
{
    int res=0;
    for(int i=l;i<=r;i++)
    {
       res=res*10+num[i]; 
    }
    return res;
}

void dfs(int u)
{
    if(u==9)
    {
        for(int i=0;i<7;i++)//注意!!!因为是递归,所以最后填入的数字在第一个,所以从0开始,并且最后一个数字是0,输出数组应该是:9,8,7,6,5,4,3,2,1,0
            for(int j=i+1;j<8;j++)
            {
                int a=calc(0,i);
                int b=calc(i+1,j);
                int c=calc(j+1,8);
                
                if(n*c==c*a+b)
                    ans++;
            }
            return;
    }
    
 
    for(int i=1;i<N;i++)//这里这里!因为是递归,所以i=1继续往下走,直到走到9,再回溯依次把数填上
        if(!used[i])
        {
            num[u]=i;
            used[i]=true; 
            dfs(u+1);
            used[i]=false;
        }
}

int main()
{
    cin>>n;
    dfs(0);
    cout<<ans;
    cout<<endl;
    return 0;
    
}

先对9个数字全排列,再用两个隔板隔开,形成a,b,c 

非暴力解法

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;

const int N=20;

int n,ans;
bool st[N],backup[N];

bool check(int a,int c)
{
    int b=n*c-c*a;
    
    if(!a||!b||!c)
        return false;
    
    memcpy(backup,st,sizeof st);
    while(b)
    {
        int x=b%10;
        b/=10;
        if(!x||backup[x]) return false;
        backup[x]=true;
    }
    for(int i=1;i<=9;i++)
        if(!backup[i])
        return false;
    
    return true;
}

void dfs_c(int u,int a,int c)
{
    if(u==n)return;
    
    if(check(a,c)) ans++;
    
    for(int i=1;i<=9;i++)
        if(!st[i])
        {
            st[i]=true;
            dfs_c(u+1,a,c*10+i);
            st[i]=false;
        }
    
}

void dfs_a(int u,int a)
{
    if(a>=n)
        return;
        
    if(a) dfs_c(u,a,0);
    
    for(int i=1;i<=9;i++)
        if(!st[i])
        {
            st[i]=true;
            dfs_a(u+1,a*10+i);
            st[i]=false;
        }
}


int main()
{
    cin>>n;
    
    dfs_a(0,0);
    cout<<ans<<endl;
    
    return 0;
}

在枚举a的叶子节点再次枚举c,再在c的叶子节点上,看看a,b,c是否满足判断 

递推

开关

你玩过“拉灯”游戏吗?

25盏灯排成一个 5×5的方形。

每一个灯都有一个开关,游戏者可以改变它的状态。

每一步,游戏者可以改变某一个灯的状态。

游戏者改变一个灯的状态会产生连锁反应:和这个灯上下左右相邻的灯也要相应地改变其状态。

我们用数字 1表示一盏开着的灯,用数字 0 表示关着的灯。

下面这种状态

10111
01101
10111
10000
11011

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

01111
11101
10111
10000
11011

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

01111
11001
11001
10100
11011

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

输入格式

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

以下若干行数据分为 n 组,每组数据有 55 行,每行 55 个字符。

每组数据描述了一个游戏的初始状态。

各组数据间用一个空行分隔。

输出格式

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

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

数据范围

0<n≤5

输入样例:

3
00111
01011
10001
11010
11100

11101
11101
11110
11111
11111

01111
11111
11111
11111
11111

输出样例:

3
2
-1

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>

using namespace std;

const int N=6;

char g[N][N],backup[N][N];
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],b=y+dy[i];
        if(a<0||a>=5||b<0||b>=5) continue;
        g[a][b]^=1;
    }
}

int main()
{
    int T;
    cin>>T;
    while(T--)
    {
        for(int i=0;i<5;i++) cin>>g[i];
        
        int res=10;
        for(int op=0;op<32;op++)
        {
            memcpy(backup,g,sizeof g);
            int step=0;
            
            //第一行操作
            for(int i=0;i<5;i++)
                if(op>>i&1)
                {
                    step++;
                    turn(0,i);
                }
             
            //中间三行
            for(int i=0;i<4;i++)
                for(int j=0;j<5;j++)
                if(g[i][j]=='0')
                {
                    step++;
                    turn(i+1,j);
                }
               
            //判断最后一排亮不亮,是否有解 
            bool dark=false;
            for(int i=0;i<5;i++)
                if(g[4][i]=='0')
                {
                 dark=true;
                break;  
                }
            
            if(!dark) res=min(res,step);
            memcpy(g,backup,sizeof g);
        }
        if(res>6)   res=-1;
        
        cout<<res<<endl;
    }
    return 0;
}

注意!第一行是通过枚举的方式完成的,有五位位置表示0,1,可以转换为0-32的数。从op为0开始,一个一个方案走完格子。

飞行员兄弟

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

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

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

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

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

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

输入格式

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

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

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

输出格式

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

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

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

数据范围

1≤i,j≤4

输入样例:

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

输出样例:

6
1 1
1 3
1 4
4 1
4 3
4 4
//枚举所有方案,相当于从0000 0000 0000一个个查到1111 1111 1111 1111
//而其中假如0100 1010 0100 0000,就是其中最佳方案
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<vector>

#define x first
#define y second

using namespace std;

typedef pair<int,int>PII;
//多取一个数是因为字符串后面需要加一个/0
const int N =5;
char g[N][N],backup[N][N];

int get(int x,int y)
{
    return x*4+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<PII>res;
    //1<<16相当于2的16次方,左移一次相当于*2
    for(int op=0;op<1<<16;op++)
    {
        vector<PII> temp;
        //backup记住原来初始的棋盘长什么样子,还原是为了下一轮操作从原棋盘开始
        memcpy(backup,g,sizeof g);//备份
        
        //操作
        for(int i=0;i<4;i++)
            for(int j=0;j<4;j++)
                if(op>>get(i,j)&1)
                {
                    temp.push_back({i,j});
                    turn_all(i,j);
                }

        //判断是否全亮
        bool has_closed=false;
        for(int i=0;i<4;i++)
            for(int j=0;j<4;j++)
                if(g[i][j]=='+')
                    has_closed=true;
                    
        if(has_closed==false)
        {
            if(res.empty()||res.size()>temp.size()) res=temp;
        }
        
        memcpy(g,backup,sizeof g);//还原
        
    }
        cout<<res.size()<<endl;
        for(auto op : res)cout<<op.x+1<<' '<<op.y+1<<endl;
    return 0;
}

 

  • 8
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值