码迷,mamicode.com
首页 > 其他好文 > 详细

7.18练习 DFS+递归

时间:2016-07-19 10:21:12      阅读:164      评论:0      收藏:0      [点我收藏+]

标签:

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;
}

7.18练习 DFS+递归

标签:

原文地址:http://blog.csdn.net/lyracol/article/details/51945016

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!