`
king_tt
  • 浏览: 2116737 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
社区版块
存档分类
最新评论

UVa 321 The New Villa,2B青年怒找卧室

 
阅读更多

题目链接:

UVA :http://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&category=24&page=show_problem&problem=257

POJ 1137:http://poj.org/problem?id=1137

ZOJ 1301:http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=1301


类型: 隐式图搜索


原题:

Mr. Black recently bought a villa in the countryside. Only one thing bothers him: although there are light switches in most rooms, the lights they control are often in other rooms than the switches themselves. While his estate agent saw this as a feature, Mr. Black has come to believe that the electricians were a bit absent-minded (to put it mildly) when they connected the switches to the outlets.

One night, Mr. Black came home late. While standing in the hallway, he noted that the lights in all other rooms were switched off. Unfortunately, Mr. Black was afraid of the dark, so he never dared to enter a room that had its lights out and would never switch off the lights of the room he was in.

After some thought, Mr. Black was able to use the incorrectly wired light switches to his advantage. He managed to get to his bedroom and to switch off all lights except for the one in the bedroom.

You are to write a program that, given a description of a villa, determines how to get from the hallway to the bedroom if only the hallway light is initially switched on. You may never enter a dark room, and after the last move, all lights except for the one in the bedroom must be switched off. If there are several paths to the bedroom, you have to find the one which uses the smallest number of steps, where ``move from one room to another'', ``switch on a light'' and ``switch off a light'' each count as one step.


Sample Input

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

2 1 2
2 1
1 1
1 2

0 0 0

Sample Output

Villa #1
The problem can be solved in 6 steps:
- Switch on light in room 2.
- Switch on light in room 3.
- Move to room 2.
- Switch off light in room 1.
- Move to room 3.
- Switch off light in room 2.

Villa #2
The problem cannot be solved.

题目大意:

一个2B青年赚钱了于是乐呵呵地跑去买了一栋别墅,结果却发现这栋别墅的电灯电路都接错了,每个房间里的电灯开关控制的不是这个房间的电灯,而是另一个房间的电灯~~ 2B青年很生气,后果很严重,于是严重谴责了装修工人。

一天晚上2B青年回到家里,站在大厅,发现除了大厅其它房间的电灯全部都是灭的。2B青年很怕黑,不敢走进没开灯的房间。所以问题来了,他该怎样从大厅走到自己的卧室?

2B青年还是很聪明的,他发现正好可以利用电灯开关接错而把另一个房间的电灯打开,然后就可以走进开了灯的那个房间(2B青年不会穿墙术 ,所以这两个房间当然必须是相邻的并且有门的)。

2B青年也是个很节约用电的好青年,所以当他走到卧室时,要求其它房间都必须把灭掉。

现在是你大显身手的时候,编写一个程序帮2B青年找出一个步骤最少的方案并且输出。 开灯,关灯,走进另一个房间都算是一个步骤。如果找不出方案的话,2B青年今晚就得睡大厅沙发了。。。


分析与总结:


最近做了不少这类型的题,也形成了自己做搜索题的一个基本思路过程,下面当作是小小的自我总结:


1. 首先是需要找一个“状态”, 这个状态可以看作是一个全局地图, 这题中,状态是表示各个房间的开关灯情况,以及2B青年当前站在哪一个房间里。 0表示关灯,1表示开灯,2表示2B青年站的位置(当然这个位置的灯也必须是亮的)。


2. 然后是找令“状态转移”的“动作”, 这题共有三个动作会导致状态转移:1)开灯; 2)关灯; 3)走进另一个房间。 也就是说做其中任何一个动作都会导致当前状态改变。


3. 进行搜索。在搜索函数中,会进行这几个动作,从而产生新的状态, 然后把新状态压入队列或者栈(bfs或者dfs),之后再对新状态又产生新的状态……一般都会有一个“目标状态”, 当从初始状态一直做动作直到状态转移成目标状态, 那么搜索就完成。


4. 状态重复问题, 也就是判重问题。当产生的新状态在之前已经有过了,那么这个状态就不再拓展新状态。刚开始做bfs和dfs时一般都会有一个vis数组来标记走过的状态, 如果状态比较大,就难以直接开一个数组来做,而需要用到哈希表判重,编码或者用STL中的map和set, 用map和set的效率比较低,经常会导致超时,前两者更常用。



这题我很2B的WA了好几个小时, 一直找不到原因。最后还是觉得自己的逻辑思路没有问题。 于是再找。。。找啊找。。。。

终于给我发现了原因: 一个常识性的错误!

我在保存路径时,路径的输出结果是保存在path字符串数组中,输出结果要输出:

switch …… 房间号,move to …… 房间号。

问题就出在了房间号上, 由于需要把int的数字转换成char字符串型, 所以我习惯性的这样处理:str[0]=v+'0', 其中v是房间号。那么如果v小于10的话没问题,可以如果v大于等于10,那么结果将是一个XXX不知道是什么的字符。

抓狂哭一个下午的时间就因为这个而消逝了。。。

这个血的教训我将铭记!!

/*
 *  UVa 321  The New Villa
 *  隐式图搜索, 哈希判重
 *  Time: 0.024s(UVA), 10ms(ZOJ)
 *  Author: D_Double
 *
 */
 
#include<cstdio>
#include<cstring>
#define MAXN 50000

typedef int State[11];
State que[MAXN];
int nRoom, nDoor, nSwitch, G[20][20], control[20][20];
int step[MAXN], father[MAXN], ans;//保存步数,父亲点
char action[3][35] = {"- Move to room ","- Switch on light in room ","- Switch off light in room "};
char path[MAXN][35]; // 保存路径

const int HASHSIZE = 2000000;  // 哈希表尺寸
int head[HASHSIZE], next[MAXN];

inline int BKDRHash(State &s){ // 哈希函数   
    int seed = 131; // 31 131 1313 13131 131313 etc..  
    int hash = 0;  
    for(int i=1; i<=nRoom; ++i)
        hash = hash * seed + s[i];
    return (hash & 0x7FFFFFFF) % HASHSIZE;  
}  

inline int try_to_insert(int s){
    int h = BKDRHash(que[s]);
    int u = head[h];
    while(u){
        if(memcmp(que[u], que[s], sizeof(que[s]))==0) return 0;
        u = next[u];
    }
    next[s] = head[h]; head[h] = s;
    return 1;    
}

inline bool isOk(State &s){  // 判断是否达到目标状态
    for(int i=1; i<nRoom; ++i) if(s[i]) return false;
    if(s[nRoom]==2) return true;
    return false;
}
// 执行的动作
inline bool run(State &s, int u, int v, int d){
    switch(d){
        case 0: // 进入房间 
            if(G[u][v] && s[v]){
                s[v] = 2; s[u] = 1; return true;
            }
            return false;
        case 1: // 开灯
            if(control[u][v] && !s[v]){
                s[v] = 1; return true;
            }
            return false;
        case 2: // 关灯
            if(control[u][v] && s[v]){
                s[v] = 0; return true;
            }
            return false;
    }
}

inline void init(){
    memset(head, 0, sizeof(head));
    memset(que[0], 0, sizeof(que[0])); que[0][1] = 2; // 1表示灯亮,0不亮,2表示人的位置(灯亮的)
    step[0] = 0; father[0] = -1;
}

void bfs(){
    init();
    int front=0, rear=1;
    while(front < rear){
        State &s = que[front];
        if(isOk(s)){ ans = front; return; }
        
        int u; // 获取当前在哪个房间
        for(int i=1; i<=nRoom; ++i) if(s[i]==2){
            u = i; break;
        }
        for(int v=1; v<=nRoom; ++v)if(u!=v){
            for(int j=0; j<3; ++j){
                State &t = que[rear];
                memcpy(t, s, sizeof(s));
                if(run(t, u, v, j)){
                    if(try_to_insert(rear)){
                        father[rear] = front;
                        step[rear] = step[front]+1;            
                        char str[4];
                        // 保存路径, 注意当房间号大于9时,不能直接用v+'0'
                        if(v<10){  str[0]=v+'0', str[1]='.', str[2]='\0'; }
                        else{ str[0]='1', str[1]='0', str[2]='.', str[3]='\0'; }
                        strcpy(path[rear], action[j]);
                        strcat(path[rear], str);
                        rear++;
                    }
                }
            }
        }        
        front++;
    }
    ans = -1; 
}
void print_path(int cur){
    if(father[cur]!=-1){
        print_path(father[cur]);
        printf("%s\n", path[cur]);
    }
}

int main(){
    int u,v,cas=1;
    
    while( scanf("%d%d%d",&nRoom,&nDoor,&nSwitch), nRoom){
        memset(G, 0, sizeof(G));
        memset(control, 0, sizeof(control));
        for(int i=0; i<nDoor; ++i){ scanf("%d %d",&u, &v); G[u][v]=G[v][u]=1; }
        for(int i=0; i<nSwitch; ++i) { scanf("%d %d", &u, &v); control[u][v]=1; }
        bfs();
        printf("Villa #%d\n", cas++);
        if(ans != -1){
            printf("The problem can be solved in %d steps:\n", step[ans]);
            print_path(ans);
        }else
            printf("The problem cannot be solved.\n");
        printf("\n");
    }
    return 0;
}

但是故事还未结束, 当我把程序交到UVA,ZOJ 后正在庆祝AC时, 交到POJ的评测结果再次把沉重地打击了我:WA

然后看了poj的discuss, 发现原来POJ的测试数据都是优先move进入一个房间,然后再开关灯的。这样的话,需要在搜索时,优先拓展move的状态, 稍微改了一下之后在poj成功AC:


/*
 *  POJ 1137  The New Villa
 *  隐式图搜索, 哈希判重
 *  Time: 94MS(POJ)
 *  Author: D_Double
 *
 */
 
#include<cstdio>
#include<cstring>
#define MAXN 50000

typedef int State[11];
State que[MAXN];
int nRoom, nDoor, nSwitch, G[20][20], control[20][20];
int step[MAXN], father[MAXN], ans;//保存步数,父亲点
char action[3][35] = {"- Move to room ","- Switch on light in room ","- Switch off light in room "};
char path[MAXN][35]; // 保存路径

const int HASHSIZE = 2000000;  // 哈希表尺寸
int head[HASHSIZE], next[MAXN];

inline int BKDRHash(State &s){ // 哈希函数   
    int seed = 131; // 31 131 1313 13131 131313 etc..  
    int hash = 0;  
    for(int i=1; i<=nRoom; ++i)
        hash = hash * seed + s[i];
    return (hash & 0x7FFFFFFF) % HASHSIZE;  
}  

inline int try_to_insert(int s){
    int h = BKDRHash(que[s]);
    int u = head[h];
    while(u){
        if(memcmp(que[u], que[s], sizeof(que[s]))==0) return 0;
        u = next[u];
    }
    next[s] = head[h]; head[h] = s;
    return 1;    
}

inline bool isOk(State &s){  // 判断是否达到目标状态
    for(int i=1; i<nRoom; ++i) if(s[i]) return false;
    if(s[nRoom]==2) return true;
    return false;
}
// 执行的动作
inline bool run(State &s, int u, int v, int d){
    switch(d){
        case 0: // 进入房间 
            if(G[u][v] && s[v]){
                s[v] = 2; s[u] = 1; return true;
            }
            return false;
        case 1: // 开灯
            if(control[u][v] && !s[v]){
                s[v] = 1; return true;
            }
            return false;
        case 2: // 关灯
            if(control[u][v] && s[v]){
                s[v] = 0; return true;
            }
            return false;
    }
}

inline void init(){
    memset(head, 0, sizeof(head));
    memset(que[0], 0, sizeof(que[0])); que[0][1] = 2; // 1表示灯亮,0不亮,2人的位置(灯亮的)
    step[0] = 0; father[0] = -1;
}

void bfs(){
    init();
    int front=0, rear=1;
    while(front < rear){
        State &s = que[front];
        if(isOk(s)){ ans = front; return; }
        
        int u; // 获取当前在哪个房间
        for(int i=1; i<=nRoom; ++i) if(s[i]==2){
            u = i; break;
        }
       for(int v=1; v<=nRoom; ++v)if(u!=v){  // 先进行move操作
            State &t = que[rear];
            memcpy(t, s, sizeof(s));
            if(run(t, u, v, 0)){
                if(try_to_insert(rear)){
                    father[rear] = front;
                    step[rear] = step[front]+1;
                    char str[4];  
                    // 保存路径, 注意当房间号大于9时,不能直接用v+'0'  
                    if(v<10){  str[0]=v+'0', str[1]='.', str[2]='\0'; }  
                    else{ str[0]='1', str[1]='0', str[2]='.', str[3]='\0'; }  
                    strcpy(path[rear], action[0]);  
                    strcat(path[rear], str);  
                    rear++;
                }
            }
        }
        
        for(int v=1; v<=nRoom; ++v)if(u!=v){ // 然后再开关灯
            State &t = que[rear];
            memcpy(t, s, sizeof(s));
            if(run(t, u, v, 1)){  // 开灯
                if(try_to_insert(rear)){
                    father[rear] = front;
                    step[rear] = step[front]+1;
                    // 保存路径, 注意当房间号大于9时,不能直接用v+'0'
                    char str[4];
                    if(v<10){  str[0]=v+'0', str[1]='.', str[2]='\0'; }  
                    else{ str[0]='1', str[1]='0', str[2]='.', str[3]='\0'; }  
                    strcpy(path[rear], action[1]);  
                    strcat(path[rear], str);  
                    rear++;
                }
            }
            else if(run(t, u, v, 2)){  // 关灯
               if(try_to_insert(rear)){
                    father[rear] = front;
                    step[rear] = step[front]+1;
                    // 保存路径, 注意当房间号大于9时,不能直接用v+'0'
                    char str[4];
                    if(v<10){  str[0]=v+'0', str[1]='.', str[2]='\0'; }  
                    else{ str[0]='1', str[1]='0', str[2]='.', str[3]='\0'; }  
                    strcpy(path[rear], action[2]);  
                    strcat(path[rear], str);  
                    rear++;
                }  
            }
        }       
        front++;
    }
    ans = -1; 
}
void print_path(int cur){
    if(father[cur]!=-1){
        print_path(father[cur]);
        printf("%s\n", path[cur]);
    }
}

int main(){
    int u,v,cas=1;
    
    while( scanf("%d%d%d",&nRoom,&nDoor,&nSwitch), nRoom){
        memset(G, 0, sizeof(G));
        memset(control, 0, sizeof(control));
        for(int i=0; i<nDoor; ++i){ scanf("%d %d",&u, &v); G[u][v]=G[v][u]=1; }
        for(int i=0; i<nSwitch; ++i) { scanf("%d %d", &u, &v); control[u][v]=1; }
        bfs();
        printf("Villa #%d\n", cas++);
        if(ans != -1){
            printf("The problem can be solved in %d steps:\n", step[ans]);
            print_path(ans);
        }else
            printf("The problem cannot be solved.\n");
        printf("\n");
    }
    return 0;
}


—— 生命的意义,在于赋予它意义。

原创http://blog.csdn.net/shuangde800By D_Double (转载请标明)






分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics