题目链接: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.在形成回路之后,所以的可选格子一律看成是不可走格子,所以只需一个分支去转移到下一个格子,两个分支肯定会出现重复,使得数量偏大。

转载于:https://www.cnblogs.com/DOLFAMINGO/p/8011931.html

FZU1977 Pandora adventure —— 插头DP相关推荐

  1. bzoj1814 Ural 1519 Formula 1(插头dp模板题)

    1814: Ural 1519 Formula 1 Time Limit: 1 Sec  Memory Limit: 64 MB Submit: 924  Solved: 351 [Submit][S ...

  2. [入门向选讲] 插头DP:从零概念到入门 (例题:HDU1693 COGS1283 BZOJ2310 BZOJ2331)

    转载请注明原文地址:http://www.cnblogs.com/LadyLex/p/7326874.html 最近搞了一下插头DP的基础知识--这真的是一种很锻炼人的题型-- 每一道题的状态都不一样 ...

  3. HDU4084 插头dp

    题意:给定一个图,0是不能放的,然后现在有1X1和1X2方块,最后铺满该图,使得1X1使用次数在C到D之间,1X2次数随便,问有几种放法 思路:插头DP或轮廓线,多加一维DP讨论就可以 注意插头DP状 ...

  4. POJ3133(插头dp)

    传送门:http://poj.org/problem?id=3133 Manhattan Wiring Time Limit: 5000MS   Memory Limit: 65536K       ...

  5. P3272 [SCOI2011]地板(插头DP)

    [题面链接] https://www.luogu.org/problemnew/show/P3272 [题目描述] 有一个矩阵,有些点必须放,有些点不能放,用一些L型的图形放满,求方案数 [题解] ( ...

  6. POJ 3133 Manhattan Wiring (插头DP)

    Manhattan Wiring Time Limit: 5000MS   Memory Limit: 65536K Total Submissions: 1110   Accepted: 634 D ...

  7. URAL1519 Formula 1 —— 插头DP

    题目链接:https://vjudge.net/problem/URAL-1519 1519. Formula 1 Time limit: 1.0 second Memory limit: 64 MB ...

  8. [集训队作业2018]小Z的礼物(min-max容斥,插头dp)

    传送门 这种求 "取到所有物品的期望时间" 的题一般都用 min−maxmin-maxmin−max容斥 解决: 设t(i,j)t(i,j)t(i,j)为取到格子(i,j)(i,j ...

  9. 【BZOJ1814】Ural 1519 Formula 1 插头DP

    [BZOJ1814]Ural 1519 Formula 1 题意:一个 m * n 的棋盘,有的格子存在障碍,求经过所有非障碍格子的哈密顿回路个数.(n,m<=12) 题解:插头DP板子题,刷板 ...

  10. Ural 1519. Formula 1 优美的插头DP

    今天早上学了插头DP的思想和最基础的应用,中午就开始敲了,岐哥说第一次写不要看别人代码,利用自己的理解一点点得写出来,这样才锻炼代码能力!于是下午慢慢地构思轮廓,一点点地敲出主体代码,其实是很磨蹭的, ...

最新文章

  1. Date String转换
  2. Spark 架构原理介绍 以及 job、task、stag 概念
  3. ubuntu下小键盘不能用
  4. 拦截器Intercepter和过滤器Filter的比较
  5. mysql fulltext索引
  6. 一份【软件工程】的学习指南已到达,请注意查收!!
  7. Packet for query is too large(1767212 1048576)mysql在存储图片时提示图片过大
  8. arm搭建云手机教程_教你从0开始部署阿里云服务器,阿里云服务器搭建网站教程...
  9. 获得进程id_浅谈python中的多线程和多进程(二)
  10. Java常见问题(1)navicat连接mysql报2059错误
  11. java static 块作用_java static块有什么具体的作用和注意事项?说具体点
  12. 京东质检报告要求和检测项目-京东质检报告怎么弄
  13. mysql数据库中eof_数据库eof
  14. android音乐加速软件,音乐变速器app
  15. P1629 邮递员送信-dijkstra+反向建边
  16. 我总结了五种常用聚类分析算法,推荐收藏
  17. 模仿QQ的左右滑动切换界面和下拉更新的效果
  18. 802.11n 技术简结
  19. 为什么抖音张同学这么火爆?用 Python 分析 1w+条评论数据,我发现了其中的秘密
  20. 红苹果IP代理软件 v6.2

热门文章

  1. 第二章 生成、打包、部署和管理应用程序及类型
  2. mpvue微信小程序引入腾讯地图sdk
  3. unity探索者之微信分享回调
  4. 数据库的三种状态RESTRICT、QUIESCE和SUSPEND
  5. maven 强制jdk的版本
  6. 如何手动从Exchange2007/1010边缘/集线器传输服务器卸载ScanMail for Exchange(SMEX10.0)程序...
  7. 爱上Ada语言与系统
  8. [翻译]A MAP BASED ON LASERSCANS WITHOUT GEOMETRIC INTERPRETATION
  9. 点击流日志分析项目实战开发流程
  10. zookeeper分布式原理实战解析