【图论】Nosknah 的宝藏
问题
给你一个无向图,有n个节点m条双向边,其中有p个节点必须经过,求最短的从1经过这p个节点再到n的最短距离
数据范围
n<=200 p<=12;
分析
先用floyd处理处任意两点之间的最短距离。
之后的得到的距离只于遍历这p个点的顺序有关。
可以搜索,但是12的数据显然超时,考虑使用状态压缩的动态规划。
可以用二进制串m表示状态,1表示这个物品已选,0表示未选。
f[i,j]表示当前在选i,之前选取的全部状态是j
采用记忆化搜索的形式,方程f[i,j]:=min{f[p,j-p(不选p的状态)]+map[bw[p],i]}
可以枚举最后一个选的是k,然后算出此时相应的状态,的f值,再加上map[bw[k],n]即可。
这样复杂度是12*2^12
显然很优秀。相当于在不同的状态间进行转移,无后效性。
code
program liukeke;
const maxb:int64=100000000000000000;
var
map:array[0..201,0..201] of int64;
f:array[0..12,0..5000] of int64;
bw:array[0..12] of longint;
ans:int64;
n,m,p:longint;
procedure init;
var
i,j,x,y,z:longint;
begin
readln(n,m);
for i:=1 to n do
for j:=1 to n do
map[i,j]:=maxb;
for i:=1 to m do
begin
readln(x,y,z);
if z<map[x,y] then
begin
map[x,y]:=z;
map[y,x]:=z;
end;
end;
for i:=1 to n do
map[i,i]:=0;
readln(p);
for i:=1 to p do
readln(bw[i]);
end;
procedure floyd;
var
i,j,k:longint;
begin
for k:=1 to n do
for i:=1 to n do
if i<>k then
for j:=1 to n do
if (i<>j)and(j<>k) then
if map[i,j]>map[i,k]+map[k,j] then
map[i,j]:=map[i,k]+map[k,j];
end;
function dfs(now,t:longint):int64;
var
i,temp:longint;
begin
if f[now,t]<maxb then exit(f[now,t]);
if t=0 then
begin
f[now,t]:=map[1,bw[now]];
exit(f[now,t]);
end;
for i:=1 to p do
begin
if (t and (1<<(i-1)) )=0 then continue;
temp:=t-(1<<(i-1));
if dfs(i,temp)+map[bw[i],bw[now]]<f[now,t] then
f[now,t]:=dfs(i,temp)+map[bw[i],bw[now]]
end;
exit(f[now,t]);
end;
procedure dp;
var
i,j,k:longint;
begin
for i:=1 to p do
if (map[1,bw[i]]=maxb)or(map[bw[i],n]=maxb) then
begin
writeln(-1);
exit;
end;
if map[1,n]=maxb then
begin
writeln(-1);
exit;
end;
ans:=maxb;
for i:=1 to p do
for j:=0 to 1<<p do
f[i,j]:=maxb;
for i:=1 to p do
begin
k:=1<<p-1-(1<<(i-1));
if dfs(i,k)=maxb then
begin
writeln(-1);
exit;
end
else
if dfs(i,k)+map[bw[i],n]<ans then ans:=dfs(i,k)+map[bw[i],n];
end;
writeln(ans);
end;
begin
assign(input,'gold.in');reset(input);
assign(output,'gold.out');rewrite(output);
init;
floyd;
dp;
close(input);close(output);
end.
反思
在搜索超时时,考虑动态规划的算法,特别是范围比较小,可用二进制串表示状态时,用状态压缩的动态规划