CTSC1999
方法:见 : “分层图思想”及其在信息学竞赛中的应用 (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.