专题一:递推与递归

递归

 例题

 递归实现指数型枚举

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

输入格式

输入一个整数 n。

输出格式

每行输出一种方案。

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

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

各行(不同方案)之间的顺序任意。

数据范围

1≤n≤15

输入样例:
3
输出样例:

3
2
2 3
1
1 3
1 2
1 2 3

import java.util.*;
public class Main{
    static int N=16;
    static int n;
    static int []st=new int[N];    //表状态,0表示未考虑,1表示选,2表示不选
    static void df(int u){
        if(u>n){    //递归出口,输出选择的数
            for(int i=1;i<=n;i++){
                if(st[i]==1)
                    System.out.print(i+" ");
            }
            System.out.println();
            return ;
        }
        //也可不恢复现场
        st[u] =2;   //未选择,递归第一个选的分支
        df(u+1);
        st[u] =0;   //恢复现场
        
        st[u]=1;//第二个分支,选了
        df(u+1);
        st[u]=0;//恢复现场
    }
    
    public static void main(String[] args){
        Scanner scan =new Scanner(System.in);
        n=scan.nextInt(); 
        df(1);
        scan.close();
    }
}

 递归实现排列型枚举

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

输入格式

一个整数 n。

输出格式

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

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

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

数据范围

1≤n≤9

输入样例:
3
输出样例:
1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1

import java.util.*;
public class Main{
    static int N=10;
    static int[] st=new int[N];         //存放第u个位置放什么数
    static boolean[] use=new boolean[N];//表示是否选用了i
    static int n;
    public static void dfs(int u){
        if(u>n){                    //递归出口,表示u个位置都已存放数
            for(int i=1;i<=n;i++){
                System.out.print(st[i]+" ");
            }
            System.out.println();
            return ;
        }
        //枚举每个分支
        for(int i=1;i<=n;i++){
            if(!use[i]){    //如果数i没有用过
                st[u]=i;    //第u个位置放i
                use[i]=true;
                dfs(u+1);
                //恢复现场
                st[u]=0;    //第u个位置未选用i
                use[i]= false;
            }
        } 
    }
    
    public static void main(String[] args){
        Scanner scan=new Scanner(System.in);
        n=scan.nextInt();
        dfs(1);
        scan.close();
    }
}

时间复杂度:n*(1+n+n(n-1)+......+n!)<n*3n!

递归实现组合型枚举

从 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 

import java.util.*;
public class Main{
    static int N=30;
    static int n,m;
    static int[] st=new int[N]; //存放选择的数
    public static void dfs(int u,int k){    //u表示位置。从k开始枚举
        if(m>u+n-k) return ;                       //剪枝优化,当m-u+1>n-k+1时
        if(u==m+1){                         //递归出口,u个位置都已有数
            for(int i=1;i<=m;i++){
                System.out.print(st[i]+" ");
            }
            System.out.println();
            return ;
        }
        for(int i=k;i<=n;i++){  //枚举
            st[u]=i;        //第u个位置存放i
            dfs(u+1,i+1);   //递归调用
            st[u]=0;        //第u个位置不存放i,恢复现场
        }
    }
    public static void main(String[] args){
        Scanner scan=new Scanner(System.in);
        n=scan.nextInt();
        m=scan.nextInt();
        dfs(1,1);
        scan.close();
    }
}

带分数 

题目描述:


100 可以表示为带分数的形式:100=3+69258714
还可以表示为:100=82+3546197
注意特征:带分数中,数字 1∼9 分别出现且只出现一次(不包含 0)。
类似这样的带分数,100 有 11 种表示法。

输入格式

一个正数

输出格式


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

数据范围

1≤N<106
1

输出样例


输入样例1:

100
1
输出样例1:

11
1
输入样例2:

105
1
输出样例2:

6

 费解的开关

你玩过“拉灯”游戏吗?

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

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

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

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

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

下面这种状态

10111
01101
10111
10000
11011

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

01111
11101
10111
10000
11011

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

01111
11001
11001
10100
11011

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

输入格式

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

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

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

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

输出格式

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

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

数据范围

0<n≤500

输入样例:
3
00111
01011
10001
11010
11100

11101
11101
11110
11111
11111

01111
11111
11111
11111
11111

输出样例:

3
2
-1
 思路:

1.第一行的操作确定,则整个操作确定,枚举对第一行共32种操作,对第一行的操作是解题的精髓,

2.对第2,3,4行的操作要根据其前一行的状态进行

3.检查最后一行是否符合状态

import java.util.*;
public class Main{
	static int[][] g=new int[5][5];
	static int[][] st=new int[5][5];
	static int ans=Integer.MAX_VALUE;
	static int[] dx= {0,0,1,0,-1},dy= {0,-1,0,1,0};	//原地,左下右上
	static 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&&b>=0&&a<5&&b<5) {		//控制边界
				st[a][b] ^=1;
			}
		}
	}
	static int work() {
	    //对第一行一共有32次操作
		for(int l=0;l<32;l++) {		
		    int step=0;
			for(int i=0;i<5;i++) {
				st[i]=Arrays.copyOf(g[i], g[i].length);
			}
			
			//操作第一行
			for(int i=0;i<5;i++) {
				if((l >> i&1)==1) {	//如果l对应的二进制数为1,表明要对这个灯进行一次按灯操作,否则就是不操作,从而枚举32种情况
					turn(0,i);
					step++;
				}
			}
			//对2,3,4行进行操作
			for(int i=0;i<4;i++) {	
				for(int j=0;j<5;j++) {
					if(st[i][j]==0) {
						turn(i+1,j);
						step++;
					}
				}
			}
            //判断最后一行			
			boolean succes=true;
			for(int i=0;i<5;i++) {
				if(st[4][i]==0) {	//最后一行若有不亮的,返回false
					succes=false;
					break;
				}
			}
			if(succes) {
				ans=Math.min(ans, step);
			}
		}
    //操作完返回 		
		if(ans<=6) {
			return ans;
		}else {
			return -1;
		}
	}
    public static void main(String[] args){
        Scanner scan=new Scanner(System.in);
        int n=scan.nextInt();
        for(int i=0;i<n;i++) {
            
        	for(int j=0;j<5;j++) {
        	    String s=scan.next();
        		for(int k=0;k<5;k++)
        			g[j][k]=s.charAt(k) - '0';
        	}
        	int a=work();
        	System.out.println(a);
        	ans=10;
        }
        scan.close();
    }
}

翻硬币

 

 解题思路:有解的话必有一种解

import java.util.*;
public class fanyingbi {
	static char[] a,b;
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Scanner scan=new Scanner(System.in);
		a=scan.nextLine().toCharArray();
		b=scan.nextLine().toCharArray();
		int step=0;
		for(int i=0;i<a.length-1;i++) {
			if(a[i]==b[i]) 
				continue;
			else {
				step++;
				if(a[i]=='o') {
					a[i]='*';
					if(a[i+1]=='o') {
						a[i+1]='*';
					}else {
						a[i+1]='o';
					}
				}else {
					a[i]='o';
					if(a[i+1]=='o') {
						a[i+1]='*';
					}else {
						a[i+1]='o';
					}
				}
				
			}
		}
		System.out.print(step);
		scan.close();
	}

}

飞行员兄弟

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

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

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

把手可以表示为一个 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
import java.util.*;
public class Main{
	static char[][] map=new char[4][4];
	static char[][] backup=new char[4][4];
	static int ans=Integer.MAX_VALUE;
	static String end="";
	static int get(int x,int y) {
		return x*4+y;
		
	}
	static void turn_one(int x,int y) {
		if(map[x][y]=='+')
			map[x][y]='-';
		else {
			map[x][y]='+';
		}
	}
	static 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);
	}
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Scanner scan=new Scanner(System.in);
		for(int i=0;i<4;i++) {
			map[i]=scan.nextLine().toCharArray();
		}
		//枚举2^16种情况
		for(int op=0;op<(1<<16);op++) {
			//备份
			for(int i=0;i<4;i++) {
				backup[i]=map[i].clone();
			}
			int step=0;
			String str="";
			//枚举每一个开关
			for(int i=0;i<4;i++) {
				for(int j=0;j<4;j++){
					if((op>>get(i,j)&1)==1) {
						step++;
						str+=(i+1);
						str+=(j+1);
						turn_all(i,j);
					}
				}
			}
			//判断是否全亮
			boolean has_closed=false;
			for(int i=0;i<4;i++) {
				for(int j=0;j<4;j++) {
					if(map[i][j]=='+') {
						has_closed=true;
						break;
					}
				}
			}
			if(!has_closed) {
				if(step<=ans) {
					ans=step;
					end=str;
				}
			}
			//还原
			for(int i=0;i<4;i++) {
				map[i]=backup[i].clone();
			}
		}
		System.out.println(ans);
		for(int op=0;op<end.length();op+=2) {
			System.out.println(end.charAt(op)+" "+end.charAt(op+1));
		}
	}

}

位运算

想看i的第k位是否是1,i>>k&1

op>>get(i,j)&1

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值