POI 1999 仓库管理员 Store−keeper

这道题描述的是我们玩过的经典的小游戏,推箱子。由于要求步数最少,基本的想法是BFS,记录人的位置和箱子的位置两个状态。但是状态数过多,有100*100*100*100,进一步思考可以发现记录人的绝对位置是没有必要的,因为只有人在箱子旁边的单元格内,才能推箱子,所以只需记录箱子的位置和人在箱子的方向,只有100*100*4个状态。

BFS过程中,状态扩展分类两种情况,一种是向前推箱子,步数要加1,另一种是改变人的方向。推箱子只需判断前面的位置是否是空地,如果是障碍则不能继续扩展。改变人的方向就有些复杂了,假设箱子的位置是(x0,y0),人原来的位置是(x1,y1),新的位置是(x2,y2),则要判断(x1,y1)到(x2,y2)是否存在不经过(x0,y0)的路径。简单的想法是floodfill,时间复杂度为O(NM)。注意状态判重,还有就是初始位置的确定。由于人初始不一定在箱子边,需要floodfill一下,求出人能到达的箱子旁边的位置,如果根本不能到达,显然无解。

按这种想法写出的程序是要超时的,加上些例如我们在玩推箱子的时候不能把箱子推到墙角之类无关紧要的优化,还是无法通过。思考算法的主要瓶颈就在于判断改变方向时每次都要floodfill一遍,如果能把这里优化一下,或许就能通过。

进一步思考,发现两地如果连通,必须至少存在一条路径,如果总是连通,必须至少存在两条路径。也就是说,无论箱子作为障碍挡在哪条路径上,总还有另一条路径使这两点连通。这恰好是图论中双连通分支(点双连通)的定义,即没有割点的连通分支。所以可以这样判断,对于一个连通图,如果箱子不在割点上,箱子旁边的两点(人的位置)一定连通,如果箱子在割点上,则人的两点位置是否连通,取决于两点是否同属一个双连通分支。于是我们可以预处理出图中的所有割点和双连通分支,然后每次判断两点连通只需O(1)的时间。这样就可以解决这道题了。

?[Copy to clipboard] View Code CPP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
/* 
 * Problem: POI1999 mag
 * Author: Guo Jiabao
 * Time: 2009.4.8 9:08
 * State: Solved
 * Memo: BFS 双连通分支判断
*/
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
const int MAXL=101,MAXN=10001,MAXM=MAXN*8;
const int LEFT=0,RIGHT=1,UP=2,DOWN=3;
const int dx[4]={0,0,-1,1},dy[4]={-1,1,0,0};
using namespace std;
struct state
{
	int x,y,p,step;
};
template<typename T> class linklist
{
	public:
	T v;
	linklist *next;
	linklist(T tv):v(tv){next=0;}
};
template<typename T> class Queue
{
	public:
	linklist<T> *first,*last;
	int size;
	void clear()
	{
		size=0;
		first=last=0;
	}
	void ins(T v)
	{
		size++;
		if (first)
			last=last->next=new linklist<T>(v);
		else
			first=last=new linklist<T>(v);
	}
	T pop()
	{
		T r=first->v;
		linklist<T> *t=first->next;
		size--;
		delete first;
		first=t;
		return r;
	}
};
struct edge
{
	edge *next;
	int t;
}ES[MAXM];
edge *V[MAXN];
bool field[MAXL][MAXL],vis[MAXL][MAXL];
bool hash[MAXL][MAXL][4];
int mvb[MAXL][MAXL][4][4],Map[MAXL][MAXL];
int LOW[MAXN],DFN[MAXN],PAR[MAXN],Sta1[MAXM],Sta2[MAXM],Stop,D,Bcnt;
bool isgd[MAXN],bichash[MAXN];
int N,M,Ans,EC=-1,U;
state S,P,T;
Queue<state> Q;
Queue<int> Bel[MAXN];
inline bool inrange(int x,int y)
{
	return x>=1 && x<=N && y>=1 && y<=M;
}
inline void addedge(int a,int b)
{
	ES[++EC].next=V[a];
	V[a]=ES+EC;V[a]->t=b;
	ES[++EC].next=V[b];
	V[b]=ES+EC;V[b]->t=a;
}
void init()
{
	int i,j,k,l;
	char c;
	freopen("mag.in","r",stdin);
	freopen("mag.out","w",stdout);
	scanf("%d%d",&N,&M);
	for (i=1;i<=N;i++)
	{
		do c=getchar(); while (c==10 || c==13);
		ungetc(c,stdin);
		for (j=1;j<=M;j++)
		{
			c=getchar();
			if (c=='S')
				field[i][j]=false;
			else
			{
				field[i][j]=true;
				Map[i][j]=++U;
				k=i-1,l=j;
				if (inrange(k,l) && field[k][l])
					addedge(Map[i][j],Map[k][l]);
				k=i,l=j-1;
				if (inrange(k,l) && field[k][l])
					addedge(Map[i][j],Map[k][l]);
			}
			if (c=='M')
			{
				S.x=i;S.y=j;
				S.step=0;S.p=0;
			}
			if (c=='P')
				P.x=i,P.y=j;
			if (c=='K')
				T.x=i,T.y=j;
			for (k=0;k<4;k++)
				for (l=0;l<4;l++)
					mvb[i][j][k][l]=2;
		}
	}
}
inline int getopd(int k)
{
	if (k==LEFT) return RIGHT;
	if (k==RIGHT) return LEFT;
	if (k==UP) return DOWN;
	return UP;
}
bool start(state i)
{
	int k,r;
	for (k=0;k<4;k++)
	{
		state j;
		j.x=i.x+dx[k]; j.y=i.y+dy[k];
		if (inrange(j.x,j.y) && field[j.x][j.y] && !vis[j.x][j.y])
		{
			vis[j.x][j.y]=true;
			if (j.x==P.x && j.y==P.y)
			{
				P.p=getopd(k);
				return true;
			}
			else
				r=start(j);
			if (r) return true;
		}
	}
	return false;
}
inline bool insamebic(int u,int v)
{
	linklist<int> *k;
	bool rs=false;
	for (k=Bel[u].first;k;k=k->next)
		bichash[k->v]=true;
	for (k=Bel[v].first;k;k=k->next)
		if (bichash[k->v])
		{
			rs=true;
			break;
		}
	for (k=Bel[u].first;k;k=k->next)
		bichash[k->v]=false;
	return rs;
}
bool movable(int bx,int by,int ps,int pd)
{
	if (mvb[bx][by][ps][pd]==2)
	{
		if (isgd[Map[bx][by]])
		{
			int k=ps,x,y;
			state p,q;
			p.x=bx+dx[k]; p.y=by+dy[k];
			k=pd;
			q.x=bx+dx[k]; q.y=by+dy[k];
			x=Map[p.x][p.y]; y=Map[q.x][q.y];
			mvb[bx][by][ps][pd]=insamebic(x,y);
		}
		else
			mvb[bx][by][ps][pd]=true;
	}
	return mvb[bx][by][ps][pd];
}
bool BFS()
{
	state i,j;
	int k;
	Q.clear();
	Q.ins(P);
	hash[P.x][P.y][P.p]=true;
	while (Q.size)
	{
		i=j=Q.pop();
		//move direction
		for (k=0;k<4;k++)
		{
			j.x=i.x+dx[k]; j.y=i.y+dy[k];
			if (inrange(j.x,j.y) && field[j.x][j.y])
			{
				j.x=i.x;j.y=i.y;j.p=k;
				if (!hash[j.x][j.y][j.p] && movable(i.x,i.y,i.p,j.p))
				{
					hash[j.x][j.y][j.p]=true;
					Q.ins(j);
				}
			}
		}
		j.step=i.step+1;
		//push box
		k=getopd(i.p);
		j.x=i.x+dx[k]; j.y=i.y+dy[k]; j.p=i.p;
		if (inrange(j.x,j.y) && field[j.x][j.y] && !hash[j.x][j.y][j.p])
		{
			if (j.x==T.x && j.y==T.y)
			{
				Ans=j.step;
				return true;
			}
			hash[j.x][j.y][j.p]=true;
			Q.ins(j);
		}
	}
	return false;
}
void addbic(int B,int u)
{
	linklist<int> *k;
	for (k=Bel[u].first;k;k=k->next)
		if (k->v==B)
			return;
	Bel[u].ins(B);
}
void bic(int u,int p)
{
	DFN[u]=LOW[u]=++D;
	for (edge *k=V[u];k;k=k->next)
	{
		int v=k->t;
		if (v==p) continue;
		if (DFN[v]<DFN[u]) //避免横叉边
		{
			Stop++;Sta1[Stop]=u;Sta2[Stop]=v;
			if (!DFN[v])
			{
				bic(v,u);
				if (LOW[v]<LOW[u])
					LOW[u]=LOW[v];
				if (DFN[u]<=LOW[v])
				{
					isgd[u]=true;
					int x,y;
					Bcnt++;
					do
					{
						x=Sta1[Stop];y=Sta2[Stop];
						Stop--;
						addbic(Bcnt,x);
						addbic(Bcnt,y);
					}
					while (!(x==u && y==v || x==v && y==u));
				}
			}
			else if (DFN[v]<LOW[u])
				LOW[u]=DFN[v];
		}
	}
}
void solve()
{
	bic(1,0);
	if (start(S) && BFS())
		printf("%d/n",Ans);
	else
		printf("NIE/n");
}
int main()
{
	init();
	solve();
	return 0;
}

仓库管理员

问题描述 码头仓库是一块N×M个格子的矩形,有的格子是空闲的——没有任何东西,有的格子上已经堆放了沉重的货物——太重了而不再可能被移动。 现在,仓库管理员有一项任务,要将一个小箱子推到指定的格子上去。管理员可以在仓库中移动,但不得跨过沉重的不可移动的货物和箱子。当管理 员站在与箱子相邻的格子上时,他可以做一次推动,把箱子推到另一个相邻的格子。考虑到箱子很重,仓库管理员为了节省体力,想尽量减少推箱子的次数。你能帮 帮他么? 输入文件 输入文件第一行有两个数N,M(1<=N,M<=100),表示仓库是N×M的矩形。以下有N行,每行有M个字符,表示一个格子的状态。

  • S 表示该格子上放了不可移动的沉重货物。
  • w 表示该格子上没有任何东西
  • M 表示仓库管理员初始的位置
  • P 表示箱子的初始位置
  • K 表示箱子的目标位置

输出文件 输出文件只有一行,一个数,表示仓库管理员最少要推多少次箱子。如果仓库管理员不可能将箱子推到目标位置,那么请输出NIE,表示无解。 样例输入

10 12
SSSSSSSSSSSS
SwwwwwwwSSSS
SwSSSSwwSSSS
SwSSSSwwSKSS
SwSSSSwwSwSS
SwwwwwPwwwww
SSSSSSSwSwSw
SSSSSSMwSwww
SSSSSSSSSSSS
SSSSSSSSSSSS

样例输出

7

你可能感兴趣的:(POI 1999 仓库管理员 Store−keeper)