7.18练习 DFS+递归

A 百练2811 熄灯问题

总时间限制: 1000ms 内存限制: 65536kB

描述

有一个由按钮组成的矩阵,其中每行有6个按钮,共5行。每个按钮的位置上有一盏灯。当按下一个按钮后,该按钮以及周围位置(上边、下边、左边、右边)的灯都会改变一次。即,如果灯原来是点亮的,就会被熄灭;如果灯原来是熄灭的,则会被点亮。在矩阵角上的按钮改变3盏灯的状态;在矩阵边上的按钮改变4盏灯的状态;其他的按钮改变5盏灯的状态。

在上图中,左边矩阵中用X标记的按钮表示被按下,右边的矩阵表示灯状态的改变。对矩阵中的每盏灯设置一个初始状态。请你按按钮,直至每一盏等都熄灭。与一盏灯毗邻的多个按钮被按下时,一个操作会抵消另一次操作的结果。在下图中,第2行第3、5列的按钮都被按下,因此第2行、第4列的灯的状态就不改变。

请你写一个程序,确定需要按下哪些按钮,恰好使得所有的灯都熄灭。根据上面的规则,我们知道1)第2次按下同一个按钮时,将抵消第1次按下时所产生的结果。因此,每个按钮最多只需要按下一次;2)各个按钮被按下的顺序对最终的结果没有影响;3)对第1行中每盏点亮的灯,按下第2行对应的按钮,就可以熄灭第1行的全部灯。如此重复下去,可以熄灭第1、2、3、4行的全部灯。同样,按下第1、2、3、4、5列的按钮,可以熄灭前5列的灯。

输入

5行组成,每一行包括6个数字(0或1)。相邻两个数字之间用单个空格隔开。0表示灯的初始状态是熄灭的,1表示灯的初始状态是点亮的。

输出

5行组成,每一行包括6个数字(0或1)。相邻两个数字之间用单个空格隔开。其中的1表示需要把对应的按钮按下,0则表示不需要按对应的按钮。

样例输入

0 1 1 0 1 0

1 0 0 1 1 1

0 0 1 0 0 1

1 0 0 1 0 1

0 1 1 1 0 0

样例输出

1 0 1 0 0 1

1 1 0 1 0 1

0 0 1 0 1 1

1 0 0 1 0 0

0 1 0 0 0 0

搜到的主流做法有暴力枚举第一行和高斯消元法(看不懂),不过前者的时间复杂度还低很多。

因为目的是固定的(全部熄灭),所以输入后只要确认第一行的按法,就能求出剩下几行的按法。

参考代码:

/*
(1)  puzzle[i][j]表示位置(i, j)上灯的初始状态:1 表示灯是被点亮的;0 表示灯是熄灭的。用数组
元素press[i][j]表示为了让全部的灯都熄灭,是否要按下位置(i, j)上的按钮:1 表示要按下;
0 表示不用按下。由于第0 行、第0 列和第7 列不属于按钮矩阵的范围,没有按钮,可以假
设这些位置上的灯总是熄灭的、按钮也不用按下。
(2)  根据熄灯规则,如果矩阵press 是寻找的答案,那么按照press 的第一行对矩阵中的按钮
操作之后,此时在矩阵的第一行上:
 *如果位置(1, j)上的灯是点亮的,则要按下位置(2, j)上按钮,即press[2][j]一定取1;
 *如果位置(1, j)上的灯是熄灭的,则不能按位置(2, j)上按钮,即press[2][j]一定取0。
这样依据press 的第一、二行操作矩阵中的按钮,才能保证第一行的灯全部熄灭。而对矩阵
中第三、四、五行的按钮无论进行什么样的操作,都不影响第一行各灯的状态。依此类推
(3) 函数guess(),做两件事件。(1)根据press 第一行和puzzle
数组,按照熄灯规则计算出press 其他行的值,使得矩阵第1~4 行的所有灯都熄灭。(2)判断
所计算的press 数组能否熄灭矩阵第5 行的所有灯。如果能够就返回“true”,表示找到了答
案;否则返回“false”,表示没有找到答案。
(4)函数fun(),对press 第一行的元素press[1][1]~ press
[1][6]的各种取值情况进行枚举。在每种取值情况下,分别调用guess(),看看是否找到了答
案。如果找到了答案,就返回主函数,否则继续下一种取值情况的判断
*/
#include<stdio.h>
#include<stdlib.h>
#include<math.h>
#include<string.h>
#include<algorithm>
using namespace std;
int puzzle[10][10];
int press[10][10];
bool guess()//guess函数由第一行和puzzle数组计算其他行的press数组
{
    for(int r=1;r<5;r++)
        for(int c=1;c<7;c++)//调整第r行
            press[r+1][c]=(puzzle[r][c]+press[r][c-1]+press[r][c]+press[r][c+1]+press[r-1][c])%2;
    for(int c=1;c<7;c++) //用计算得到的数组判断是否能关闭第五行
        if((press[5][c-1]+press[5][c]+press[5][c+1]+press[4][c])%2!=puzzle[5][c]) return false;
    return true;
}
void fun()
{
    int c;
    for(c=1;c<7;c++) press[1][c]=0;
    while(!guess())
    {
        press[1][1]++;
        c=1;
        while(press[1][c]>1)
        {
            press[1][c++]=0;
            press[1][c]++;
        }
    }
    return;
}
int main()
{
    for(int r=0;r<6;r++) press[r][0]=press[r][7]=0;//在两边扩充两列,防止操作时数组越界
    for(int c=1;c<7;c++) press[0][c]=0;//在上面扩充一行
    for(int r=1;r<6;r++)
        for(int c=1;c<7;c++)
            scanf("%d",&puzzle[r][c]);
    fun();
    for(int r=1;r<6;r++){
        for(int c=1;c<7;c++) printf("%d ",press[r][c]);
        printf("\n");
    }
    return 0;
}

B 百练2814 拨钟问题

有9个时钟,排成一个3*3的矩阵。

|——-| |——-| |——-|

| | | | | | |

|—O | |—O | | O |

| | | | | |

|——-| |——-| |——-|

A B C

|——-| |——-| |——-|

| | | | | |

| O | | O | | O |

| | | | | | | | |

|——-| |——-| |——-|

D E F

|——-| |——-| |——-|

| | | | | |

| O | | O—| | O |

| | | | | | | |

|——-| |——-| |——-|

G H I

(图 1)

现在需要用最少的移动,将9个时钟的指针都拨到12点的位置。共允许有9种不同的移动。如下表所示,每个移动会将若干个时钟的指针沿顺时针方向拨动90度。

移动 影响的时钟

1 ABDE

2 ABC

3 BCEF

4 ADG

5 BDEFH

6 CFI

7 DEGH

8 GHI

9 EFHI

输入

9个整数,表示各时钟指针的起始位置,相邻两个整数之间用单个空格隔开。其中,0=12点、1=3点、2=6点、3=9点。

输出

输出一个最短的移动序列,使得9个时钟的指针都指向12点。按照移动的序号从小到大输出结果。相邻两个整数之间用单个空格隔开。

样例输入

3 3 0

2 2 2

2 1 2

样例输出

4 5 8 9

DFS框架很容易写,但是9种操作写起来非常麻烦。

一开始写了200多行,WA也找不出问题出在哪,最后参考了PKU大神的代码,精简代码也是一门学问。

参考代码:

#include<stdio.h>
#include<stdlib.h>
#include<math.h>
#include<string.h>
#include<algorithm>
using namespace std;
const int table[10][5]={{},{1,2,4,5},{1,2,3},{2,3,5,6},
{1,4,7},{2,4,5,6,8},{3,6,9},{4,5,7,8},{7,8,9},{5,6,8,9}};
int state[10],times[10],ans[10];
int mi=0x3f3f3f3f;
bool first=1;
void dfs(int n,int dep)//dep:步数
{
    if(n==10)
    {
        for(int i=1;i<=9;i++) if(state[i]%4!=0) return;
        if(dep<mi)
        {
            mi=dep;
            for(int i=1;i<=9;i++) ans[i]=times[i];
        }
        return;
    }
    for(times[n]=0;times[n]<4;times[n]++) //使用方法n的次数
    {
        for(int i=0;i<5;i++) state[table[n][i]]+=times[n];//改变该方法下第i个被调整的钟的状态
        dfs(n+1,dep+times[n]);
        for(int i=0;i<5;i++) state[table[n][i]]-=times[n];//恢复全局变量
    }
    return;
}
int main()
{
    for(int i=1;i<=9;i++) scanf("%d",&state[i]);
    dfs(1,0);
    for(int i=1;i<=9;i++)
        for(int j=0;j<ans[i];j++)//使用方法i时,打印i
        {
            if(first) first=0;
            else printf(" ");
            printf("%d",i);
        }
    printf("\n");
    return 0;
}

C 百练2756 二叉树

描述

如上图所示,由正整数1, 2, 3, …组成了一棵无限大的二叉树。从某一个结点到根结点(编号是1的结点)都有一条唯一的路径,比如从10到根结点的路径是(10, 5, 2, 1),从4到根结点的路径是(4, 2, 1),从根结点1到根结点的路径上只包含一个结点1,因此路径就是(1)。对于两个结点x和y,假设他们到根结点的路径分别是(x1, x2, … ,1)和(y1, y2, … ,1)(这里显然有x = x1,y = y1),那么必然存在两个正整数i和j,使得从xi 和 yj开始,有xi = yj , xi + 1 = yj + 1, xi + 2 = yj + 2,… 现在的问题就是,给定x和y,要求xi(也就是yj)。

输入

输入只有一行,包括两个正整数x和y,这两个正整数都不大于1000。

输出

输出只有一个正整数xi。

样例输入

10 4

样例输出

2

AC代码:

#include<stdio.h>
#include<stdlib.h>
#include<math.h>
#include<string.h>
#include<algorithm>
using namespace std;
int father(int a, int b)
{
    if (a == b) return a;
    else
    {
        if(a>b) return father(a/2,b);
        else return father(a,b/2);
    }
}
int main()
{
    int a,b;
    scanf("%d%d",&a,&b);
    printf("%d\n",father(a,b));
    return 0;
}

递归求共同祖先。

不用递归写得更快:

#include <iostream>
using namespace std;
int main () {
int x,y;
cin>>x>>y;
while (x!=y)
(x>y)?x/=2:y/=2;
cout<<x<<endl;
return 0;
}

D 百练1664 放苹果

描述

把M个同样的苹果放在N个同样的盘子里,允许有的盘子空着不放,问共有多少种不同的分法?(用K表示)5,1,1和1,5,1 是同一种分法。

输入

第一行是测试数据的数目t(0 <= t <= 20)。以下每行均包含二个整数M和N,以空格分开。1<=M,N<=10。

输出

对输入的每组数据M和N,用一行输出相应的K。

样例输入

1

7 3

样例输出

8

将n分成k个数的和表示成fun(n,k),可以分成两种情况,一种是k个数中含有0,则可以拿走其中的一个0,分法数与fun(n,k-1)相等。另外一种情况是不含0,则每个数至少为1,即转化为fun(n-k,k)。所以fun(n,k) = fun(n,k-1) + fun(n-k,k)。

当n>m,苹果最多只能占满m个盘子,多出的盘子可以忽视。

AC代码:

#include<stdio.h>
#include<stdlib.h>
#include<math.h>
#include<string.h>
#include<algorithm>

using namespace std;
int fun(int m,int n)
{
    if(n==1||m==0) return 1;
    if(m<n) return fun(m,m);
    return fun(m,n-1)+fun(m-n,n);
}
int main()
{
    int t;
    scanf("%d",&t);
    int a,b;
    while(t--)
    {
        scanf("%d%d",&a,&b);
        int ans=fun(a,b);
        printf("%d\n",ans);
    }
    return 0;
}

E 百练2816 红与黑

描述

有一间长方形的房子,地上铺了红色、黑色两种颜色的正方形瓷砖。你站在其中一块黑色的瓷砖上,只能向相邻的黑色瓷砖移动。请写一个程序,计算你总共能够到达多少块黑色的瓷砖。

输入

包括多个数据集合。每个数据集合的第一行是两个整数W和H,分别表示x方向和y方向瓷砖的数量。W和H都不超过20。在接下来的H行中,每行包括W个字符。每个字符表示一块瓷砖的颜色,规则如下

1)‘.’:黑色的瓷砖;

2)‘#’:白色的瓷砖;

3)‘@’:黑色的瓷砖,并且你站在这块瓷砖上。该字符在每个数据集合中唯一出现一次。

当在一行中读入的是两个零时,表示输入结束。

输出

对每个数据集合,分别输出一行,显示你从初始位置出发能到达的瓷砖数(记数时包括初始位置的瓷砖)。

样例输入

6 9

….#.

…..#

……

……

……

……

……

#@…#

.#..#.

0 0

样例输出

45

标准的DFS,数据范围很小,所以就算写法很奇葩也能过。

搜了个比较正常的写法

AC代码:

#include<stdio.h>
#include<stdlib.h>
#include<math.h>
#include<string.h>
#include<algorithm>
using namespace std;
char pic[50][50];
int vis[50][50];
int cnt=0;
int h,w;
void dfs(int x,int y)
{
    if(x<0||y<0||x>=h||y>=w) return;
    if(pic[x][y]!=‘.‘&&pic[x][y]!=‘@‘) return;
    if(vis[x][y]>9) return;
    if(vis[x][y]==0)
    {
        cnt++;
    }
    vis[x][y]++;
    for(int i=0;i<4;i++)
    {
        if(i==0) dfs(x,y+1);
        if(i==1) dfs(x,y-1);
        if(i==2) dfs(x-1,y);
        if(i==3) dfs(x+1,y);
    }
}
int main()
{
    while(scanf("%d%d",&w,&h)!=0)
    {
        if(w==0&&h==0) break;
        memset(vis,0,sizeof(vis));
        int yes=0;
        int m,n;
        for(int i=0;i<h;i++) scanf("%s",pic[i]);
        for(int i=0;i<h;i++)
            for(int j=0;j<w;j++)
            {
                if(pic[i][j]==‘@‘)
                {
                    m=i,n=j;
                    yes=1;
                    break;
                }
                if(yes) break;
            }
        dfs(m,n);
        printf("%d\n",cnt);
        cnt=0;
    }
    return 0;
}

F 百练2754 八皇后

描述

会下国际象棋的人都很清楚:皇后可以在横、竖、斜线上不限步数地吃掉其他棋子。如何将8个皇后放在棋盘上(有8 * 8个方格),使它们谁也不能被吃掉!这就是著名的八皇后问题。

对于某个满足要求的8皇后的摆放方法,定义一个皇后串a与之对应,即a=b1b2…b8,其中bi为相应摆法中第i行皇后所处的列数。已经知道8皇后问题一共有92组解(即92个不同的皇后串)。

给出一个数b,要求输出第b个串。串的比较是这样的:皇后串x置于皇后串y之前,当且仅当将x视为整数时比y小。

输入

第1行是测试数据的组数n,后面跟着n行输入。每组测试数据占1行,包括一个正整数b(1 <= b <= 92)

输出

输出有n行,每行输出对应一个输入。输出应是一个正整数,是对应于b的皇后串。

样例输入

2

1

92

样例输出

15863724

84136275

标准八皇后问题的简单变形,加上用于储存当前皇后所在位置的临时数组,每次找到解后放进一个大数组。

AC代码:

#include<stdio.h>
#include<stdlib.h>
#include<math.h>
#include<string.h>
#include<algorithm>
using namespace std;
int str[100][10];
int tem[10];
int c[10];
int tot=0;
void dfs(int cur)
{
    if(cur==8)
    {
    for(int i=0;i<9;i++) str[tot][i]=tem[i];
    tot++;
    //printf("%d\n",tot);
    return;
    }
    else for(int i=0;i<8;i++)
    {
        int ok=1;
        c[cur]=i;
        for(int j=0;j<cur;j++)
            if(c[cur]==c[j]||cur-c[cur]==j-c[j]||cur+c[cur]==j+c[j])
                {
                    ok=0;break;
                }
        if(ok)
        {
            tem[cur]=i;
            dfs(cur+1);
        }
    }
}
int main()
{
    dfs(0);
    int n;
    scanf("%d",&n);
    while(n--)
    {
        int b;
        scanf("%d",&b);
        for(int i=0;i<8;i++) printf("%d",str[b-1][i]+1);
        printf("\n");
    }
    return 0;
}

G 百练2817 木棒

描述

乔治拿来一组等长的木棒,将它们随机地裁断,使得每一节木棍的长度都不超过50个长度单位。然后他又想把这些木棍恢复到为裁截前的状态,但忘记了初始时有多少木棒以及木棒的初始长度。请你设计一个程序,帮助乔治计算木棒的可能最小长度。每一节木棍的长度都用大于零的整数表示。

输入

输入包含多组数据,每组数据包括两行。第一行是一个不超过64的整数,表示砍断之后共有多少节木棍。第二行是截断以后,所得到的各节木棍的长度。在最后一组数据之后,是一个零。

输出

为每组数据,分别输出原始木棒的可能最小长度,每组数据占一行。

样例输入

9

5 2 1 5 2 1 5 2 1

4

1 2 3 4

0

样例输出

6

5

直接搜题解了。

好像要剪很多次枝才能过。

参考代码:

#include<iostream>
#include<algorithm>
using namespace std;
const int Max = 65;

int n, len, stick[Max];
bool flag, vis[Max];

bool cmp(int a, int b){
    return a > b;
}

void dfs(int dep, int now_len, int u){   // dep为当前已被用过的小棒数,u为当前要处理的小棒。
    if(flag) return;
    if(now_len == 0){                    //  当前长度为0,寻找下一个当前最长小棒。
        int k = 0;
        while(vis[k]) k ++;              //  寻找第一个当前最长小棒。
        vis[k] = true;
        dfs(dep + 1, stick[k], k + 1);
        vis[k] = false;
        return;
    }
    if(now_len == len){                  //  当前长度为len,即又拼凑成了一根原棒。
        if(dep == n) flag = true;        //  完成的标志:所有的n根小棒都有拼到了。
        else dfs(dep, 0, 0);
        return;
    }
    for(int i = u; i < n; i ++)
        if(!vis[i] && now_len + stick[i] <= len){
            if(!vis[i-1] && stick[i] == stick[i-1]) continue;      //  不重复搜索:最重要的剪枝。
            vis[i] = true;
            dfs(dep + 1, now_len + stick[i], i + 1);
            vis[i] = false;
        }
}

int main(){
    while(scanf("%d", &n) && n != 0){
        int sum = 0;
        flag = false;
        for(int i = 0; i < n; i ++){
            scanf("%d", &stick[i]);
            sum += stick[i];
        }
        sort(stick, stick + n, cmp);     //  从大到小排序。
        for(len = stick[0]; len < sum; len ++)
            if(sum % len == 0){          //  枚举能被sum整除的长度。
                memset(vis, 0, sizeof(vis));
                dfs(0, 0, 0);
                if(flag) break;
            }
        printf("%d\n", len);
    }
    return 0;
}
时间: 2024-11-09 00:42:18

7.18练习 DFS+递归的相关文章

poj1416——dfs递归枚举+记录路径

POJ 1416  dfs递归枚举+记录路径 Shredding Company Time Limit: 1000MS   Memory Limit: 10000K Total Submissions: 4456   Accepted: 2555 Description You have just been put in charge of developing a new shredder for the Shredding Company Although a "normal" s

POJ 1321-棋盘问题(DFS 递归)

POJ 1321-棋盘问题 K - DFS Time Limit:1000MS     Memory Limit:10000KB     64bit IO Format:%I64d & %I64u Description 在一个给定形状的棋盘(形状可能是不规则的)上面摆放棋子,棋子没有区别.要求摆放时任意的两个棋子不能放在棋盘中的同一行或者同一列,请编程求解对于给定形状和大小的棋盘,摆放k个棋子的所有可行的摆放方案C. Input 输入含有多组测试数据. 每组数据的第一行是两个正整数,n k,用

poj2531——dfs递归枚举+小剪枝

POJ 2531  dfs递归枚举 Network Saboteur Time Limit: 2000MS   Memory Limit: 65536K Total Submissions: 9580   Accepted: 4560 Description A university network is composed of N computers. System administrators gathered information on the traffic between nodes

[leetcode 40. 组合总和 II] 不排序使用哈希表+dfs递归 vs 排序栈+回溯

题目描述 给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合. candidates 中的每个数字在每个组合中只能使用一次. 说明: 所有数字(包括目标数)都是正整数. 解集不能包含重复的组合. 示例 1: 输入: candidates = [10,1,2,7,6,1,5], target = 8, 所求解集为: [ [1, 7], [1, 2, 5], [2, 6], [1, 1, 6] ] 示例 2: 输入

递归基础—组合数 / 排列数—输出的各种办法(dfs/递归/bfs)

B: 部分和问题***(注意部分和 ! = 任意子区间求和不一样) 描述   给你N个数,问你能不能从其中取出一些,让它们的和为K. 输入 第一行包括两个数,N,K,分别代表数字个数,以及和为K. 接下来N行,每行一个数字. 输出 如果能选出一些数和为K, 输出YE5,  否则,输出N0 样例 输入: 4 0 1 -1  2  3 输出: YE5 输入: 2 2 1 -3 输出: N0 本题求组合数和,注意pe之外,思维比较基础,办法很多,以下为利用dfs思想实现的一种办法 如果求组合数输出各种

洛谷 P2089 烤鸡【DFS递归/10重枚举】

[链接]:https://www.luogu.org/problemnew/show/P2089 题目描述 猪猪Hanke特别喜欢吃烤鸡(本是同畜牲,相煎何太急!)Hanke吃鸡很特别,为什么特别呢?因为他有10种配料(芥末.孜然等),每种配料可以放1—3克,任意烤鸡的美味程度为所有配料质量之和 现在,Hanke想要知道,如果给你一个美味程度,请输出这10种配料的所有搭配方案 输入输出格式 输入格式: 一行,n<=5000 输出格式: 第一行,方案总数 第二行至结束,10个数,表示每种配料所放的

hdu1997 汉诺塔VII(DFS递归调用)

题目详情:传送门 我都要做郁闷了,逻辑一直没错,但是最后一组答案就是过不了.看了几个小时,终于发现问题所在了.我把数组初始化 memset() 函数,放在了自定义函数 Input 中,使用形参的sizeof()作为地址的长度,结果数组没有初始化成功,导致悲剧的诞生.之后我把 memset() 中的地址长度改回数组长度问题终于解决了.刚做这一题时我把它当成栈混洗了,结果一直没琢磨明白.之后在网上一查,恍然大悟.霎时间,感觉好难过,为什么自己就没想到.下面我们来分析一下本题的思路吧. 分析: 这一题

JAVA_SE基础——18.方法的递归

方法的递归是指在一个方法的内部调用自身的过程,递归必须要有结束条件,不然就会陷入无限递归的状态,永远无法结束调用,接下来用一个最简单的例子来体现下方法递归,使用递归算法计算自然数之和: public class Example18 { <span style="white-space:pre"> </span>public static void main(String[] args) { <span style="white-space:pre

18. java面向对象 - 递归

一.案例 public class Recursive { public int sumRec(int num){ if(num == 1){ return 1; }else { return num + sumRec(num -1); } } } class RecTest{ public static void main(String[] args) { Recursive rec = new Recursive(); System.out.println(rec.sumRec(100));