HDOJ(HDU).1015 Safecracker (DFS)

HDOJ(HDU).1015 Safecracker [从零开始DFS(2)]

从零开始DFS
HDOJ.1342 Lotto [从零开始DFS(0)] — DFS思想与框架/双重DFS
HDOJ.1010 Tempter of the Bone [从零开始DFS(1)] —DFS四向搜索/奇偶剪枝
HDOJ(HDU).1015 Safecracker [从零开始DFS(2)] —DFS四向搜索变种
HDOJ(HDU).1016 Prime Ring Problem (DFS) [从零开始DFS(3)] —小结:做DFS题目的关注点
HDOJ(HDU).1035 Robot Motion [从零开始DFS(4)]—DFS题目练习
HDOJ(HDU).1241 Oil Deposits(DFS) [从零开始DFS(5)] —DFS八向搜索/双重for循环遍历
HDOJ(HDU).1258 Sum It Up (DFS) [从零开始DFS(6)] —DFS双重搜索/去重技巧
HDOJ(HDU).1045 Fire Net [从零开始DFS(7)]—DFS练习/check函数的思想

题意分析

首先默认有个字母到数字的映射,A=1,B=2,C=3 …… Z=26,给出你一个目标数字target,然后给出一串字母,要求从这些字母中选取5个数字vwxyz,满足
v - w^2 + x^3 - y^4 + z^5 = target。可能有多组解,只输出字典序最大的那个。

嗨呀一看到这道题,是否有种似曾相识的感觉。很像 HDOJ.1342 Lotto [从零开始DFS(0)]这道题。题目的大意都是选数字,满足某种要求。我们分析一下这道题和HDOJ.1342的异同:

1.HDOJ.1342给出的数据是有序的因此我们直接想选择/不选,找到解的时候就直接是按照字典序由小到大输出的。
但是本题给出来的字母的序列是无序的因此想要只输出一组字典序最大的解就需要按照降序排序,然后再处理。

2.HDOJ.1342中每个数字面临的情况是选或者不选,只要位数凑够6即可。但是此题不能按照这样的想法,因为按照顺序判定选或者不选的时候,这样可能丢解。举个例子:
给定的字母序列(已经按照降序排列好):ZXVUNMDBA
按照之前的思路,我们只需要依次判定Z选/不选,X选/不选……A选/不选。把选定的5个数字依次填在v, w, x, y, z的位置上。但是如果
ZBVUN是一组解的话,按照上面的思路根本搜索不到。

Tip:B明显比V小

解决方法:想起 HDOJ.1010 Tempter of the Bone [从零开始DFS(1)]中的4方向搜索的环节了吗?只需要对dfs函数稍加改动,再用visit数组判断是否选择过此数字即可。

HDOJ.1010 中的四方向搜索:

    for(int i = 0;i<4;++i){
        if(!visit[x+spx[i]][y+spy[i]]&&!judge){
            dfs(x+spx[i],y+spy[i],s+1);
            visit[x+spx[i]][y+spy[i]] = false;
        }
    }

Tip: 我们按照这样式的,采用for循环来实现。

上代码,掰开了揉碎了慢慢来。。

代码总览

/*
    HDOJ.1015
    Author:pengwill
    Date:2017-2-5
*/
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
char a[15], b[10];
int visit[15];
int n,num,len;
bool judge = false;

bool cmp(char a, char b)
{
    return a>b;
}
void init()
{
    len = strlen(a);
    judge = false;
    memset(visit,0,sizeof(visit));
    sort(a,a+len,cmp);
    for(int i = 0;i<len;++i){
        a[i] = a[i] -'A' + 1;
    }
}
void lnit()
{
    for(int i = 0;i<5;++i)
        b[i] = b[i]+'A'-1;
}
bool check()
{
    if(n == b[0] - b[1]*b[1] + b[2]*b[2]*b[2] - b[3]*b[3]*b[3]*b[3] + b[4]*b[4]*b[4]*b[4]*b[4]){
        judge = true;
        return true;
    }else return false;
}
void dfs(int depth)
{
    //递归边界
    if(judge) return;
    if(depth == 5) {check(); return;}
    for(int i = 0;i<len;++i){
        if(!visit[i]&&!judge){
            b[depth] = a[i];
            visit[i] = 1;
            dfs(depth+1);
            visit[i] = 0;
        }
    }
}
int main()
{

    //freopen("in.txt","r",stdin);
    while(scanf("%d %s",&n,a)&& !(n==0 && !strcmp(a,"END"))){
        init();
        dfs(0);
        lnit();
        if(judge)printf("%s
",b);
        else printf("no solution
");
    }
    return 0;
}

main函数中完成了数据的读入,init函数把字符数组a中的字母转变成数字,lnit函数在最后输出的时候把数组b中的答案又转变成了字母,check函数就是检查是否是满足题目要求的解。关键在dfs函数。

void dfs(int depth)
{
    //递归边界
    if(judge) return;
    if(depth == 5) {check(); return;}
    for(int i = 0;i<len;++i){
        if(!visit[i]&&!judge){
            b[depth] = a[i];
            visit[i] = 1;
            dfs(depth+1);
            visit[i] = 0;
        }
    }
}

不要忘记题目要求:找到一组字典序最大的解即可。首先是递归边界,如果找到了解(judge为真),停止递归;亦或是当depth为5(代表找到了5个数字)的时候,用check函数判断一下是否满足题目要求。若都不满足递归边界,继续搜索。
下面的for循环非常像dfs地图的四向搜索,但是len指的是数据中给定的字母序列的长度。那么就指,下一个搜索的目标要在所有的字母序列中找,哪些可以作为搜索目标呢?首先就是这个字母没有被选定过(!visit[i] )并且现在解还没有找到(!judge)。 进入if后,首先把数组b中depth的位置赋值为a[i],代表我数组b选定了你a中i这个位置的数字(或者说是字母),并且在visit中置为选择过了,dfs(depth+1)继续寻找下一个位置的搜索目标。别忘了最后把visit[i]置为0(无后效性)。

相比前边2题,此题的收获就在于:原先的地图四向搜索,也可以变成这样从几个字符,数字中寻找可行的解。活学活用,非常重要呀!

原文地址:https://www.cnblogs.com/pengwill/p/7367174.html