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

2023-12-10 04:58
文章标签 new 卧室 uva 青年 2b 321 villa

本文主要是介绍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 22 1 2
2 1
1 1
1 20 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 50000typedef 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);}elseprintf("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 50000typedef 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);}elseprintf("The problem cannot be solved.\n");printf("\n");}return 0;
}


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

 

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






这篇关于UVa 321 The New Villa,2B青年怒找卧室的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/476070

相关文章

uva 10055 uva 10071 uva 10300(水题两三道)

情歌两三首,水题两三道。 好久没敲代码了为暑假大作战热热身。 uva 10055 Hashmat the Brave Warrior 求俩数相减。 两个debug的地方,一个是longlong,一个是输入顺序。 代码: #include<stdio.h>int main(){long long a, b;//debugwhile(scanf("%lld%lld", &

poj 3259 uva 558 Wormholes(bellman最短路负权回路判断)

poj 3259: 题意:John的农场里n块地,m条路连接两块地,w个虫洞,虫洞是一条单向路,不但会把你传送到目的地,而且时间会倒退Ts。 任务是求你会不会在从某块地出发后又回来,看到了离开之前的自己。 判断树中是否存在负权回路就ok了。 bellman代码: #include<stdio.h>const int MaxN = 501;//农场数const int

poj 2349 Arctic Network uva 10369(prim or kruscal最小生成树)

题目很麻烦,因为不熟悉最小生成树的算法调试了好久。 感觉网上的题目解释都没说得很清楚,不适合新手。自己写一个。 题意:给你点的坐标,然后两点间可以有两种方式来通信:第一种是卫星通信,第二种是无线电通信。 卫星通信:任何两个有卫星频道的点间都可以直接建立连接,与点间的距离无关; 无线电通信:两个点之间的距离不能超过D,无线电收发器的功率越大,D越大,越昂贵。 计算无线电收发器D

uva 10387 Billiard(简单几何)

题意是一个球从矩形的中点出发,告诉你小球与矩形两条边的碰撞次数与小球回到原点的时间,求小球出发时的角度和小球的速度。 简单的几何问题,小球每与竖边碰撞一次,向右扩展一个相同的矩形;每与横边碰撞一次,向上扩展一个相同的矩形。 可以发现,扩展矩形的路径和在当前矩形中的每一段路径相同,当小球回到出发点时,一条直线的路径刚好经过最后一个扩展矩形的中心点。 最后扩展的路径和横边竖边恰好组成一个直

uva 10061 How many zero's and how many digits ?(不同进制阶乘末尾几个0)+poj 1401

题意是求在base进制下的 n!的结果有几位数,末尾有几个0。 想起刚开始的时候做的一道10进制下的n阶乘末尾有几个零,以及之前有做过的一道n阶乘的位数。 当时都是在10进制下的。 10进制下的做法是: 1. n阶位数:直接 lg(n!)就是得数的位数。 2. n阶末尾0的个数:由于2 * 5 将会在得数中以0的形式存在,所以计算2或者计算5,由于因子中出现5必然出现2,所以直接一

uva 568 Just the Facts(n!打表递推)

题意是求n!的末尾第一个不为0的数字。 不用大数,特别的处理。 代码: #include <stdio.h>const int maxn = 10000 + 1;int f[maxn];int main(){#ifdef LOCALfreopen("in.txt", "r", stdin);#endif // LOCALf[0] = 1;for (int i = 1; i <=

uva 575 Skew Binary(位运算)

求第一个以(2^(k+1)-1)为进制的数。 数据不大,可以直接搞。 代码: #include <stdio.h>#include <string.h>const int maxn = 100 + 5;int main(){char num[maxn];while (scanf("%s", num) == 1){if (num[0] == '0')break;int len =

uva 10014 Simple calculations(数学推导)

直接按照题意来推导最后的结果就行了。 开始的时候只做到了第一个推导,第二次没有继续下去。 代码: #include<stdio.h>int main(){int T, n, i;double a, aa, sum, temp, ans;scanf("%d", &T);while(T--){scanf("%d", &n);scanf("%lf", &first);scanf

uva 10916 Factstone Benchmark(打表)

题意是求 k ! <= 2 ^ n ,的最小k。 由于n比较大,大到 2 ^ 20 次方,所以 2 ^ 2 ^ 20比较难算,所以做一些基础的数学变换。 对不等式两边同时取log2,得: log2(k ! ) <=  log2(2 ^ n)= n,即:log2(1) + log2(2) + log2 (3) + log2(4) + ... + log2(k) <= n ,其中 n 为 2 ^

uva 10025 The ? 1 ? 2 ? ... ? n = k problem(数学)

题意是    ?  1  ?  2  ?  ...  ?  n = k 式子中给k,? 处可以填 + 也可以填 - ,问最小满足条件的n。 e.g k = 12  - 1 + 2 + 3 + 4 + 5 + 6 - 7 = 12 with n = 7。 先给证明,令 S(n) = 1 + 2 + 3 + 4 + 5 + .... + n 暴搜n,搜出当 S(n) >=