原题:
http://codewaysky.sinaapp.com/problem.php?id=1055
题目描述
1944年,特种兵麦克接到国防部的命令,要求立即赶赴太平洋上的一个孤岛,营救被敌军俘虏的大兵瑞恩。瑞恩被关押在一个迷宫里,迷宫地形复杂,但是幸好麦克得到了迷宫的地形图。
迷宫的外形是一个长方形,其在南北方向被划分为N行,在东西方向被划分为M列,于是整个迷宫被划分为N×M个单元。我们用一个有序数对(单元的行号,单元的列号)来表示单元位置。南北或东西方向相邻的两个单元之间可以互通,或者存在一扇锁着的门,又或者存在一堵不可逾越的墙。迷宫中有一些单元存放着钥匙,并且所有的门被分为P类,打开同一类的门的钥匙相同,打开不同类的门的钥匙不同。
大兵瑞恩被关押在迷宫的东南角,即(N,M)单元里,并已经昏迷。迷宫只有一个入口,在西北角,也就是说,麦克可以直接进入(1,1)单元。另外,麦克从一个单元移动到另一个相邻单元的时间为1,拿取所在单元的钥匙的时间以及用钥匙开门的时间忽略不计。
输入
第一行是三个整数,依次表示N,M,P的值;(3≤N,M≤15,1≤P≤10)
第二行是一个整数K,表示迷宫中门和墙的总个数;
第i+2行(1≤i≤K),有5个整数,依次为Xi1,Yi1,Xi2,Yi2,Gi:
当Gi≥1 时,表示(Xi1,Yi1)单元与(Xi2,Yi2) 单元之间有一扇第Gi类的门,当Gi=0时, 表示(Xi1,Yi1)单元与(Xi2,Yi2) 单元之间有一堵不可逾越的墙;
(其中,|Xi1-Xi2|+|Yi1-Yi2|=1,0≤Gi≤P)
第K+3 行是一个整数S,表示迷宫中存放的钥匙总数;
第K+3+j行(1≤j≤S),有3个整数,依次为Xi1,Yi1,Qi:表示第j把钥匙存放在(Xi1,Yi1) 单元里,并且第j把钥匙是用来开启第Qi类门的。(其中1≤Qi≤P)
注意:输入数据中同一行各相邻整数之间用一个空格分隔。
输出
对每组数据只输出一个整数T,表示麦克营救到大兵瑞恩的最短时间的值,若不存在可行的营救方案则输出-1。
样例输入
样例输出
提示
本题共6个测试点
来源
方法:见 : “分层图思想”及其在信息学竞赛中的应用 (IOI2004国家集训队论文)
另:
[转] http://blog.sina.com.cn/s/blog_7ff7f6960100shiz.html
算法:分层图最短路
在不考虑有门和钥匙的时候,就是简单的最短路,把相邻且没有墙的格子两两相连,权值为一,然后求最短路就可以。
加入门的限制后,无法仅仅用最短路解决,所以将原图(用单独的数组存储格子之间的关系)复制2^p份,num[I,j,i1,i2,i3,i4,i5,i6,i7,i8,i9,i10]记录在拥有不同钥匙的情况下的节点编号,I记录行,j记录列,i1~i10取值范围为[0..1],0表示没有该种钥匙,1表示有(多少把都可以),将有关系的节点之间连边,再整体做一遍SPFA就可以得到最优解
有关系的节点:
1)相邻节点:中间没有墙和门或者有门但是两节点都在有该门钥匙的图上,则两节点间连一条权值为1的无向边
2)对应同一位置,且该位置有钥匙,且一个节点位于无该钥匙的图上,另一节点位于有该钥匙的图上,则从无钥匙的节点向有钥匙的节点连一条权值为0的有向边
注:最优情况下不一定有全部的钥匙,即i1~i10不一定都取1
总结:这道题用了快一天的时间,一上午都没有写出来的主要原因还是不敢用多重循环(十层),对节点记录的方法也没有想到,下午在做了一道同样是分层图最短 路的题后,有了记录节点编号的方法,构图也就相对容易了,加上十层循环后,基本上没有用太多的时间调试就AC了,遇到比较复杂,用最短路无法直接解决的问 题,应该考虑到分层,将一个复杂的问题转化为多个简单的问题来解决
代码:
const
dx: array[1..4] of -1..1=(0,1,0,-1);
dy: array[1..4] of -1..1=(1,0,-1,0);
type
point=^node;
node=record
next: point;
y, d: longint;
end;
re=record
kx, ky, can: longint;
ss: longint;
end;
var
i, j, k, n, m, ans, p: longint;
ks, sum, tot: longint;
num: array[0..15,0..15,0..1,0..1,0..1,0..1,0..1,0..1,0..1,0..1,0..1,0..1] of longint;
a: array[0..240000] of node; //邻接表存储图
dis: array[0..400000] of longint;
b: array[0..400000] of boolean;
h: array[0..4000000] of longint;
key: array[0..11] of re; //记录钥匙的相关信息
g: array[0..15,0..15,0..15,0..15] of longint; //存储最原始的图,即两节点之间是否有障碍,有的话具体是什么
x1, x2, y1, y2, q: longint;
num1, num2: longint;
each: array[0..16,0..16,0..11] of longint; //因为一个格子内可能有多把钥匙,所以需要记录数量
link: array[0..16,0..16,0..11] of longint; //记录在[I,j]格子内,第k把钥匙是什么
i1, i2, i3, i4, i5, i6, i7, i8, i9, i10: longint;
na: longint;
procedure ins(i,j,w: longint);
var
pp: point;
begin
new(pp);
pp^.y := j;
pp^.d := w;
pp^.next := a[i].next;
a[i].next := pp;
end;
procedure buildgragh; //构图
var
i, j, k: longint;
ii: longint;
xx, yy, tmp: longint;
z: array[1..10] of longint;
//i1, i2, i3, i4, i5, i6, i7, i8, i9, i10: longint;
begin
for i := 1 to n do
for j := 1 to m do
for z[1] := 0 to key[1].ss do
for z[2] := 0 to key[2].ss do
for z[3] := 0 to key[3].ss do
for z[4] := 0 to key[4].ss do
for z[5] := 0 to key[5].ss do
for z[6] := 0 to key[6].ss do
for z[7] := 0 to key[7].ss do
for z[8] := 0 to key[8].ss do
for z[9] := 0 to key[9].ss do
for z[10] := 0 to key[10].ss do begin //十重循环来枚举当前所拥有钥匙的状态
for ii := 1 to 4 do begin
xx := i+dx[ii];
yy := j+dy[ii];
if (xx>0) and (xx<=n) and (yy>0) and (yy<=m) and (g[i,j,xx,yy]<>-1) then begin
if g[i,j,xx,yy]=0 then begin //没有障碍
ins(num[i,j,z[1],z[2],z[3],z[4],z[5],z[6],z[7],z[8],z[9],z[10]],num[xx,yy,z[1],z[2],z[3],z[4],z[5],z[6],z[7],z[8],z[9],z[10]],1);
ins(num[xx,yy,z[1],z[2],z[3],z[4],z[5],z[6],z[7],z[8],z[9],z[10]],num[i,j,z[1],z[2],z[3],z[4],z[5],z[6],z[7],z[8],z[9],z[10]],1);
end else begin //有障碍但是能够打开
tmp := g[i,j,xx,yy];
if z[tmp]=1 then begin
ins(num[i,j,z[1],z[2],z[3],z[4],z[5],z[6],z[7],z[8],z[9],z[10]],num[xx,yy,z[1],z[2],z[3],z[4],z[5],z[6],z[7],z[8],z[9],z[10]],1);
ins(num[xx,yy,z[1],z[2],z[3],z[4],z[5],z[6],z[7],z[8],z[9],z[10]],num[i,j,z[1],z[2],z[3],z[4],z[5],z[6],z[7],z[8],z[9],z[10]],1);
end;
end;
end;
end;
if (each[i,j,0]>0) then begin //当前格子有钥匙
for k := 1 to each[i,j,0] do if (z[link[i,j,k]]=1) then begin //当前节点在有这把钥匙的图上
tmp := link[i,j,k];
xx := num[i,j,z[1],z[2],z[3],z[4],z[5],z[6],z[7],z[8],z[9],z[10]];
case tmp of
1:yy := num[i,j,0,z[2],z[3],z[4],z[5],z[6],z[7],z[8],z[9],z[10]];
2:yy := num[i,j,z[1],0,z[3],z[4],z[5],z[6],z[7],z[8],z[9],z[10]];
3:yy := num[i,j,z[1],z[2],0,z[4],z[5],z[6],z[7],z[8],z[9],z[10]];
4:yy := num[i,j,z[1],z[2],z[3],0,z[5],z[6],z[7],z[8],z[9],z[10]];
5:yy := num[i,j,z[1],z[2],z[3],z[4],0,z[6],z[7],z[8],z[9],z[10]];
6:yy := num[i,j,z[1],z[2],z[3],z[4],z[5],0,z[7],z[8],z[9],z[10]];
7:yy := num[i,j,z[1],z[2],z[3],z[4],z[5],z[6],0,z[8],z[9],z[10]];
8:yy := num[i,j,z[1],z[2],z[3],z[4],z[5],z[6],z[7],0,z[9],z[10]];
9:yy := num[i,j,z[1],z[2],z[3],z[4],z[5],z[6],z[7],z[8],0,z[10]];
10:yy := num[i,j,z[1],z[2],z[3],z[4],z[5],z[6],z[7],z[8],z[9],0];
end;
ins(yy,xx,0); //与代表同一格子,但是在无该钥匙的图上的节点相连
end;
end;
end;
end;
procedure spfa(st: longint);
var
i, j, k, head, tail, now: longint;
pp: point;
begin
head := 1;
tail := 1;
fillchar(b,sizeof(b),false);
b[st] := true;
fillchar(dis,sizeof(dis),100);
na := dis[3];
dis[st] := 0;
h[1] := st;
while head<=tail do begin
now := h[head];
new(pp);
pp := a[now].next;
while pp<>nil do begin
j := pp^.y;
if dis[now]+pp^.d<dis[j] then begin
dis[j] := dis[now]+pp^.d;
if not b[j] then begin
b[j] := true;
inc(tail);
h[tail] := j;
end;
end;
pp := pp^.next;
end;
b[now] := false;
inc(head);
end;
end;
begin
assign(input,'d:\01.in');
reset(input);
assign(output,'d:\01.out');
rewrite(output);
readln(n,m,p);
readln(sum);
fillchar(g,sizeof(g),0);
for i := 1 to sum do begin
readln(x1,y1,x2,y2,q);
if q<>0 then g[x1,y1,x2,y2] := q else g[x1,y1,x2,y2] := -1;
g[x2,y2,x1,y1] := g[x1,y1,x2,y2];
end;
readln(ks);
fillchar(key,sizeof(key),0);
fillchar(link,sizeof(link),0);
fillchar(each,sizeof(each),0);
for i := 1 to ks do begin
readln(x1,y1,q);
key[q].kx := x1;
key[q].ky := y1;
//inc(key[q].ss);
key[q].ss := 1; //不用管具体有几把,只要有这种钥匙,就赋为1(用于循环)
inc(each[x1,y1,0]);
link[x1,y1,each[x1,y1,0]] := q; //记录这个格子的钥匙情况
end;
tot := 0;
for i := 1 to n do
for j := 1 to m do if ks>=1 then
for i1 := 0 to key[1].ss do //if ks>=2 then
for i2 := 0 to key[2].ss do //if ks>=3 then
for i3 := 0 to key[3].ss do //if ks>=4 then
for i4 := 0 to key[4].ss do //if ks>=5 then
for i5 := 0 to key[5].ss do //if ks>=6 then
for i6 := 0 to key[6].ss do //if ks>=7 then
for i7 := 0 to key[7].ss do //if ks>=8 then
for i8 := 0 to key[8].ss do //if ks>=9 then
for i9 := 0 to key[9].ss do //if ks>=10 then
for i10 := 0 to key[10].ss do begin
inc(tot);
num[i,j,i1,i2,i3,i4,i5,i6,i7,i8,i9,i10] := tot;
a[tot].next := nil;
end; //初始化,给分层图的节点编号
// writeln(tot);
buildgragh;
spfa(num[1,1,0,0,0,0,0,0,0,0,0,0]); //最初是没有任何钥匙的
ans := maxlongint;
// for i := 1 to 10 do writeln(key[i].ss);
for i1 := 0 to key[1].ss do
for i2 := 0 to key[2].ss do
for i3 := 0 to key[3].ss do
for i4 := 0 to key[4].ss do
for i5 := 0 to key[5].ss do
for i6 := 0 to key[6].ss do
for i7 := 0 to key[7].ss do
for i8 := 0 to key[8].ss do
for i9 := 0 to key[9].ss do
for i10 := 0 to key[10].ss do begin
// writeln(dis[num[n,m,i1,i2,i3,i4,i5,i6,i7,i8,i9,i10]]);
if dis[num[n,m,i1,i2,i3,i4,i5,i6,i7,i8,i9,i10]]<ans
then ans := dis[num[n,m,i1,i2,i3,i4,i5,i6,i7,i8,i9,i10]];
end;
//writeln(ans);
if ans=na then writeln(-1) else writeln(ans);
close(input);
close(output);
end.