FZU1977 Pandora adventure —— 插头DP

题目链接:https://vjudge.net/problem/FZU-1977

 

 Problem 1977 Pandora adventure

Accept: 597    Submit: 2199
Time Limit: 1000 mSec    Memory Limit : 32768 KB

 Problem Description

The pollution of the earth is so serious that people can not survive any more. Fortunately, people have found a new planet that maybe has life, and we call it "Pandora Planet".

 

Leonardo Da Vinci is the only astronaut on the earth. He will be sent to the Pandora Planet to gather some plant specimens and go back. The plant specimen is important to the people to decide whether the planet is fit to live or not.

 

Assuming that Da Vinci can only move in an N×M grid. The positions of the plant specimens he wants to collect are all marked by the satellite. His task is to find a path to collect all the plant specimens and return to the spaceship. There are some savage beasts in the planet. Da Vinci can not investigate the grid with the savage beast. These grids are also marked by the satellite. In order to save time Da Vinci could only visit each grid exactly once and also return to the start grid, that is, you can not visit a grid twice except the start grid. You should note that you can choose any grid as the start grid.

 

Now he wants to know the number of different paths he can collect all the plant specimens. We only care about the path and ignore where the start grid is, so the two paths in Figure 1 are considered as the same.

Figure 1

 Input

The first line of the input contains an integer T (T≤100), indicating the number of cases. Each case begins with a line containing two integers N and M (1≤N, M≤12), the size of the planet is N×M. Each of the following N lines contains M characters Gij(1≤i≤N, 1≤j≤M), Gij denotes the status of the grid in row i and column j, where 'X' denotes the grid with savage beast, '*' denotes the safe grid that you can decide to go or not, 'O' denotes the plant specimen you should collect. We guarantee that there are at least three plant specimens in the map.

 Output

For each test case, print a line containing the test case number (beginning with 1) and the number of different paths he can collect all the plant specimens. You can make sure that the answer will fit in a 64-bit signed integer.

 Sample Input

2 2 2 OO O* 4 4 ***O XO** **O* XX**

 Sample Output

Case 1: 1 Case 2: 7

 Source

The 35th ACM/ICPC Asia Regional Fuzhou Site —— Online Contest

 

 

题意:

给出一个n*m(n、m<=12)的棋盘,‘X’表示不可走格子, ‘*’表示可选格子, ‘O’表示必走格子,问有多少种回路,使得回路经过所有必走格刚好一次?

 

题解:

1.此题(URAL1519 Formula 1 )的加强版,多了“可选格子”。

2.怎么处理这个“可选格子”呢?答:

1) 对于当前格子,如果他是可选格子,且没有左、上插头。由于他是“可选”的,那么我们就可以走出两个分支:一是作为必走格子新建连通分量,而是作为不可走格子,直接转移到下一个格子。

2) 由于可选的格子加入,就不能像以往记录最后一个必走格子来结束回路了,因为在必走格子之后,可能还有可选格子,而可选格子同样可以作为结束回路的格子。

3) 为了解决如何结束回路的问题,给出了两种解决方案,两种方案都需要添加一个标记isend来记录是否已经形成回路。

 

方案一:

对于可走的格子(可选或者必走),如果它有左、上插头,且这两个插头属于同一个分量,那么我们就把他们接上(不管当前格子是否为最后一个必走格),形成了一个回路。然后,我们再去判断是否还有其他连通分量,如果有的话,那么这种方案肯定不合法,应当舍弃;如果没有其他连通分量,那么我们就暂且当它是合法的,再接下来的过程中,如果发现还有必走点,那么这种方案也就被发现是不合法的,所以也应当抛弃。

代码如下:

  1 #include <iostream>
  2 #include <cstdio>
  3 #include <cstring>
  4 #include <algorithm>
  5 #include <vector>
  6 #include <cmath>
  7 #include <queue>
  8 #include <stack>
  9 #include <map>
 10 #include <string>
 11 #include <set>
 12 using namespace std;
 13 typedef long long LL;
 14 const int INF = 2e9;
 15 const LL LNF = 9e18;
 16 const int MOD = 1e9+7;
 17 const int MAXN = 1e5;
 18 const int HASH = 1e4;
 19 
 20 int n, m;
 21 int maze[15][15];
 22 
 23 struct
 24 {
 25     int size, head[HASH], next[MAXN];
 26     LL state[MAXN], sum[MAXN];
 27 
 28     void init()
 29     {
 30         size = 0;
 31         memset(head, -1, sizeof(head));
 32     }
 33 
 34     void insert(LL status, LL Sum)
 35     {
 36         int u = status%HASH;
 37         for(int i = head[u]; i!=-1; i = next[i])
 38         {
 39             if(state[i]==status)
 40             {
 41                 sum[i] += Sum;
 42                 return;
 43             }
 44         }
 45         state[size] = status;   //头插法
 46         sum[size] = Sum;
 47         next[size] = head[u];
 48         head[u] = size++;
 49     }
 50 
 51 }Hash_map[2];
 52 
 53 struct
 54 {
 55     int code[13];   //用于记录轮廓线上每个位置的插头状态
 56     int isend;        //标记是否已经结束,即形成回路
 57     LL encode(int m)    //编码:把轮廓线上的信息压缩到一个longlong类型中
 58     {
 59         LL status = isend;
 60         int id[13], cnt = 0;
 61         memset(id, -1, sizeof(id));
 62         id[0] = 0;
 63         for(int i = m; i>=0; i--)   //从高位到低位。为每个连通块重新编号,采用最小表示法。
 64         {
 65             if(id[code[i]]==-1) id[code[i]] = ++cnt;
 66             code[i] = id[code[i]];
 67             status <<= 3;   //编码
 68             status += code[i];
 69         }
 70         return status;
 71     }
 72 
 73     void decode(int m, LL status)  //解码:将longlong类型中轮廓线上的信息解码到数组中
 74     {
 75         memset(code, 0, sizeof(code));
 76         for(int i = 0; i<=m; i++)   //从低位到高位
 77         {
 78             code[i] = status&7;
 79             status >>= 3;
 80         }
 81         isend = status&1;
 82     }
 83 
 84     void shift(int m)   //左移:在每次转行的时候都需要执行。
 85     {
 86         for(int i = m-1; i>=0; i--)
 87             code[i+1] = code[i];
 88         code[0] = 0;
 89     }
 90 
 91 }Line;
 92 
 93 void transfer_blank(int i, int j, int cur)
 94 {
 95     for(int k = 0; k<Hash_map[cur].size; k++)   //枚举上一个格子所有合法的状态
 96     {
 97         LL status = Hash_map[cur].state[k]; //得到状态
 98         LL Sum = Hash_map[cur].sum[k];      //得到数量
 99         Line.decode(m, status);             //对状态进行解码
100         int up = Line.code[j];         //得到上插头
101         int left = Line.code[j-1];     //得到下插头
102 
103         if(Line.isend)
104         {
105             if(maze[i][j]==1) continue; //如果已经结束了,但在后面却出现了必走格,说明这种情况非法。
106             Line.code[j] = Line.code[j-1] = 0;  //否则,这个格就是可选格,在结束后就视为不可走格,直接转移
107             if(j==m) Line.shift(m);
108             Hash_map[cur^1].insert(Line.encode(m), Sum);
109             continue;
110         }
111 
112         if(!up && !left)        //没有上、左插头,新建分量
113         {
114             if(maze[i+1][j] && maze[i][j+1])    //如果新建的两个插头所指向的两个格子可行,新建的分量才合法
115             {
116                 Line.code[j] = Line.code[j-1] = 6;  //为新的分量编号,最大的状态才为6
117                 Hash_map[cur^1].insert(Line.encode(m), Sum);
118             }
119             if(maze[i][j]==2)   //如果为可选点,那么在没有插头的时候,可以选择不走
120             {
121                 Line.code[j] = Line.code[j-1] = 0;
122                 if(j==m) Line.shift(m);
123                 Hash_map[cur^1].insert(Line.encode(m), Sum);
124             }
125         }
126         else if( (left&&!up) || (!left&&up) )   //仅有其中一个插头,延续分量
127         {
128             int line = left?left:up;    //记录是哪一个插头
129             if(maze[i+1][j])        //往下延伸
130             {
131                 Line.code[j-1] = line;
132                 Line.code[j] = 0;
133                 if(j==m) Line.shift(m);
134                 Hash_map[cur^1].insert(Line.encode(m), Sum);
135             }
136             if(maze[i][j+1])    //往右延伸
137             {
138                 Line.code[j-1] = 0;
139                 Line.code[j] = line;
140                 Hash_map[cur^1].insert(Line.encode(m), Sum);
141             }
142         }
143         else    //上、左插头都存在,尝试合并。
144         {
145             if(up!=left)    //如果两个插头属于两个联通分量,那么就合并
146             {
147                 Line.code[j] = Line.code[j-1] = 0;
148                 for(int t = 0; t<=m; t++)   //随便选一个编号最为他们合并后分量的编号
149                     if(Line.code[t]==up)
150                         Line.code[t] = left;
151                 if(j==m) Line.shift(m);
152                 Hash_map[cur^1].insert(Line.encode(m), Sum);
153             }
154             else    //由于不确定哪个是结束格,所以就不再根据结束格来判断,而是暂且认为这种情况合法,到后面才排除非法的
155             {
156                 Line.code[j] = Line.code[j-1] = 0;
157                 if(j==m) Line.shift(m);
158                 Line.isend = 1;
159                 for(int t = 0; t<=m; t++)   //合并后看看是否只有一个连通分量
160                     if(Line.code[t])
161                         Line.isend = 0;
162                 if(Line.isend)
163                     Hash_map[cur^1].insert(Line.encode(m), Sum);
164             }
165         }
166     }
167 }
168 
169 void transfer_block(int i, int j, int cur)
170 {
171     for(int k = 0; k<Hash_map[cur].size; k++)
172     {
173         LL status = Hash_map[cur].state[k]; //得到状态
174         LL Sum = Hash_map[cur].sum[k];      //得到数量
175         Line.decode(m, status);
176         if(j==m) Line.shift(m);
177         Hash_map[cur^1].insert(Line.encode(m), Sum);
178     }
179 }
180 
181 int main()
182 {
183     int T, kase = 0;
184     scanf("%d", &T);
185     while(T--)
186     {
187         char s[15];
188         scanf("%d%d", &n, &m);
189         memset(maze, false, sizeof(maze));
190         for(int i = 1; i<=n; i++)
191         {
192             scanf("%s", s+1);
193             for(int j = 1; j<=m; j++)
194             {
195                 if(s[j]=='X') maze[i][j] = 0;
196                 else if(s[j]=='O') maze[i][j] = 1;
197                 else maze[i][j] = 2;
198             }
199         }
200 
201         int cur = 0;
202         Hash_map[cur].init();
203         Hash_map[cur].insert(0, 1);
204         for(int i = 1; i<=n; i++)
205         for(int j = 1; j<=m; j++)
206         {
207             Hash_map[cur^1].init();
208             if(!maze[i][j])
209                 transfer_block(i, j ,cur);
210             else
211                 transfer_blank(i, j, cur);
212             cur ^= 1;
213         }
214 
215         LL last_status = 0;
216         LL ans = Hash_map[cur].size?Hash_map[cur].sum[last_status]:0;
217         printf("Case %d: %I64d\n", ++kase, ans);
218     }
219 }
View Code

 

方案二:

跟方案一类似,只不过把 “在形成回路后,如果发现了必走点,就舍弃” 这个判断提前了。即:在形成回路的时候,既要判断是否还有其他连通分量,也要判断后面是否还有必走格。

代码如下:

  1 #include <iostream>
  2 #include <cstdio>
  3 #include <cstring>
  4 #include <algorithm>
  5 #include <vector>
  6 #include <cmath>
  7 #include <queue>
  8 #include <stack>
  9 #include <map>
 10 #include <string>
 11 #include <set>
 12 using namespace std;
 13 typedef long long LL;
 14 const int INF = 2e9;
 15 const LL LNF = 9e18;
 16 const int MOD = 1e9+7;
 17 const int MAXN = 1e5;
 18 const int HASH = 1e4;
 19 
 20 int n, m, last_x, last_y;
 21 int maze[15][15];
 22 bool hav[15][15];
 23 
 24 struct
 25 {
 26     int size, head[HASH], next[MAXN];
 27     LL state[MAXN], sum[MAXN];
 28 
 29     void init()
 30     {
 31         size = 0;
 32         memset(head, -1, sizeof(head));
 33     }
 34 
 35     void insert(LL status, LL Sum)
 36     {
 37         int u = status%HASH;
 38         for(int i = head[u]; i!=-1; i = next[i])
 39         {
 40             if(state[i]==status)
 41             {
 42                 sum[i] += Sum;
 43                 return;
 44             }
 45         }
 46         state[size] = status;   //头插法
 47         sum[size] = Sum;
 48         next[size] = head[u];
 49         head[u] = size++;
 50     }
 51 
 52 }Hash_map[2];
 53 
 54 struct
 55 {
 56     int code[13];   //用于记录轮廓线上每个位置的插头状态
 57     int isend;
 58     LL encode(int m)    //编码:把轮廓线上的信息压缩到一个longlong类型中
 59     {
 60         LL status = isend;
 61         int id[13], cnt = 0;
 62         memset(id, -1, sizeof(id));
 63         id[0] = 0;
 64         for(int i = m; i>=0; i--)   //从高位到低位。为每个连通块重新编号,采用最小表示法。
 65         {
 66             if(id[code[i]]==-1) id[code[i]] = ++cnt;
 67             code[i] = id[code[i]];
 68             status <<= 3;   //编码
 69             status += code[i];
 70         }
 71         return status;
 72     }
 73 
 74     void decode(int m, LL status)  //解码:将longlong类型中轮廓线上的信息解码到数组中
 75     {
 76         memset(code, 0, sizeof(code));
 77         for(int i = 0; i<=m; i++)   //从低位到高位
 78         {
 79             code[i] = status&7;
 80             status >>= 3;
 81         }
 82         isend = status&1;
 83     }
 84 
 85     void shift(int m)   //左移:在每次转行的时候都需要执行。
 86     {
 87         for(int i = m-1; i>=0; i--)
 88             code[i+1] = code[i];
 89         code[0] = 0;
 90     }
 91 
 92 }Line;
 93 
 94 void transfer_blank(int i, int j, int cur)
 95 {
 96     for(int k = 0; k<Hash_map[cur].size; k++)   //枚举上一个格子所有合法的状态
 97     {
 98         LL status = Hash_map[cur].state[k]; //得到状态
 99         LL Sum = Hash_map[cur].sum[k];      //得到数量
100         Line.decode(m, status);             //对状态进行解码
101         int up = Line.code[j];         //得到上插头
102         int left = Line.code[j-1];     //得到下插头
103 
104         if(Line.isend)  //如果已经结束了,那么说明这个格是可选格,在此视为不可走格,直接转移到下一个格
105         {
106             Line.code[j] = Line.code[j-1] = 0;
107             if(j==m) Line.shift(m);
108             Hash_map[cur^1].insert(Line.encode(m), Sum);
109             continue;
110         }
111 
112         if(!up && !left)        //没有上、左插头,新建分量
113         {
114             if(maze[i+1][j] && maze[i][j+1])    //如果新建的两个插头所指向的两个格子可行,新建的分量才合法
115             {
116                 Line.code[j] = Line.code[j-1] = 6;  //为新的分量编号,最大的状态才为6
117                 Hash_map[cur^1].insert(Line.encode(m), Sum);
118             }
119             if(maze[i][j]==2)
120             {
121                 Line.code[j] = Line.code[j-1] = 0;
122                 if(j==m) Line.shift(m);
123                 Hash_map[cur^1].insert(Line.encode(m), Sum);
124             }
125         }
126         else if( (left&&!up) || (!left&&up) )   //仅有其中一个插头,延续分量
127         {
128             int line = left?left:up;    //记录是哪一个插头
129             if(maze[i+1][j])        //往下延伸
130             {
131                 Line.code[j-1] = line;
132                 Line.code[j] = 0;
133                 if(j==m) Line.shift(m);
134                 Hash_map[cur^1].insert(Line.encode(m), Sum);
135             }
136             if(maze[i][j+1])    //往右延伸
137             {
138                 Line.code[j-1] = 0;
139                 Line.code[j] = line;
140                 Hash_map[cur^1].insert(Line.encode(m), Sum);
141             }
142         }
143         else    //上、左插头都存在,尝试合并。
144         {
145             if(up!=left)    //如果两个插头属于两个联通分量,那么就合并
146             {
147                 Line.code[j] = Line.code[j-1] = 0;
148                 for(int t = 0; t<=m; t++)   //随便选一个编号最为他们合并后分量的编号
149                     if(Line.code[t]==up)
150                         Line.code[t] = left;
151                 if(j==m) Line.shift(m);
152                 Hash_map[cur^1].insert(Line.encode(m), Sum);
153             }
154             else if(!hav[i][j])  //后面没有必走格子
155             {
156                 Line.code[j] = Line.code[j-1] = 0;
157                 if(j==m) Line.shift(m);
158                 Line.isend = 1;
159                 for(int t = 0; t<=m; t++)   //合并后看看是否只有一个连通分量
160                     if(Line.code[t])
161                         Line.isend = 0;
162                 if(Line.isend)
163                     Hash_map[cur^1].insert(Line.encode(m), Sum);
164             }
165         }
166     }
167 }
168 
169 void transfer_block(int i, int j, int cur)
170 {
171     for(int k = 0; k<Hash_map[cur].size; k++)
172     {
173         LL status = Hash_map[cur].state[k]; //得到状态
174         LL Sum = Hash_map[cur].sum[k];      //得到数量
175         Line.decode(m, status);
176         if(j==m) Line.shift(m);
177         Hash_map[cur^1].insert(Line.encode(m), Sum);
178     }
179 }
180 
181 int main()
182 {
183     int T, kase = 0;
184     scanf("%d", &T);
185     while(T--)
186     {
187         char s[15];
188         scanf("%d%d", &n, &m);
189         memset(maze, false, sizeof(maze));
190         for(int i = 1; i<=n; i++)
191         {
192             scanf("%s", s+1);
193             for(int j = 1; j<=m; j++)
194             {
195                 if(s[j]=='X') maze[i][j] = 0;
196                 else if(s[j]=='*') maze[i][j] = 2;
197                 else
198                 {
199                     maze[i][j] = 1;
200                     last_x = i;
201                     last_y = j;
202                 }
203             }
204         }
205         memset(hav, false, sizeof(hav));
206         for(int i = 1; i<=n; i++)
207             for(int j = 1; j<=m; j++)
208                 if(i<last_x || (i==last_x&&j<last_y))
209                     hav[i][j] = true;
210 
211         int cur = 0;
212         Hash_map[cur].init();
213         Hash_map[cur].insert(0, 1);
214         for(int i = 1; i<=n; i++)
215         for(int j = 1; j<=m; j++)
216         {
217             Hash_map[cur^1].init();
218             if(!maze[i][j])
219                 transfer_block(i, j ,cur);
220             else
221                 transfer_blank(i, j, cur);
222             cur ^= 1;
223         }
224 
225         LL last_status = 0;
226         LL ans = Hash_map[cur].size?Hash_map[cur].sum[last_status]:0;
227         printf("Case %d: %I64d\n", ++kase, ans);
228     }
229 }
View Code

 

 

错误分析:

一开始的时候,我是是这样写的:

if(!maze[i][j])
    transfer_block(i, j ,cur);
else if(maze[i][j]==1)
    transfer_blank(i, j, cur);
else
{
    transfer_block(i, j ,cur);
    transfer_blank(i, j, cur);
}

即:如果当前格子是可选格子,那么就可以分两个分支进行转移。

但是统计数会偏多,因为:

1.在上一个格子转移的时候,因为把当前格子看作是可走格子,所以就可以有插头引向当前格子。然后到了转移这个格子的时候,如果把当前格子看做是不可走格子,那么它的前提是没有插头引过来。然后在上一个格子转移的时候,就既有插头的情况,也有没有的情况,所以数量肯定偏大。解决方法是:把当前格子当成不可走格子时,加个判断,判断是否含有插头。

2.在形成回路之后,所以的可选格子一律看成是不可走格子,所以只需一个分支去转移到下一个格子,两个分支肯定会出现重复,使得数量偏大。

 

posted on 2017-12-09 16:44  h_z_cong  阅读(232)  评论(0编辑  收藏  举报

导航