访问(最短路+搜索剪枝)

2247: 访问(deliver)

题目描述

给你一个n个顶点的邻接矩阵(图),以及每个顶点的访问时限,要求从顶点1开始,寻找一个访问序列,要求在每个顶点的访问时限之前访问,且每个顶点的访问时间之和最小

输入

第一行一个数n,2<=n<=30。
下面n行为一个n*n的邻接矩阵,其中的第i行第j列元素值为顶点i到顶点j之间的消耗时间(integer以内)。 
最后一行为n-1个数(longint以内),分别表示第2到第n个顶点的访问时限。

输出

一行一个数,表示按要求访问完所有顶点的最少时间。如果不能完成则输出一个-1。

样例输入

4
0 3 8 6
4 0 7 4
7 5 0 2
6 9 3 0
30 8 30

样例输出

36

刚看到这道题时,我认真地看了n多遍的样例。怎么算都是37,于是我愉快地认为样例错了。然后就GG了。

先解释一波样例,n=4,然后一个4*4的矩阵,用来描述两点之间的距离,最后一行描述2~4个点的访问时限。

根据2,3,4的时限分别为30,8,30可知,必须先从1到3。然后可以由3到2或4,显然,先到4所用时间较少。最后从4到2(关键来了),这时候,我就认为从4直接到2,于是8+(8+2)+(8+2+9)=37(注意,是每个点的访问时间之和,所以时间要累加),然后就GG了。然而,正解是这样的,从4先到3在从3到2。那么答案就变成了8+(8+2)+(8+2+3+5)=36(很重要的一点,点是可以重复走来使时间消耗尽量少的)。所以,很明显可以用最短路的思想,又由于本题n范围较小,所以直接弗洛伊德。求出最短路后,再进行回溯搜索,不断模拟路径。当然直接搜索是行不通的,绝对是会超时的。所以需要加上剪枝。两个比较好想的剪枝。

1、根据答案最小剪枝,当当前的总时间+访问到当前这个点的时间*剩下的点数(假设到剩下的点的时间都为0)>最小总时间时exit

2、根据每个点的时限剪枝,for循环一遍搜索,一但当前时间+当前所在的点到i点的时间超过i点的时限时exit

贴个代码(pascal)

var n,i,j,min,k:longint;
a,f:array[1..2000,1..2000] of longint;
p,t:array[1..100000] of longint;
function min1(x,y:longint):longint;
 begin
  if x>y then exit(y)
  else exit(x);
 end;
procedure dfs(t1,t2,ans:longint);{t1为当前到达的点,t2为当前已经做到第t2个点,ans为当前的总时间}
var i:longint;
 begin
  if t2=n then begin if ans<min then min:=ans; exit;end;
  for i:=2 to n do
   if (p[i]=0)and(p[t1]+f[t1,i]>t[i]) then exit;{根据时限剪枝}
    if ans+p[t1]*(n-t2)>=min then exit;{根据答案剪枝}
  for i:=2 to n do
   begin
    if p[i]=0 then
     begin
      p[i]:=p[t1]+f[t1,i];
      dfs(i,t2+1,ans+p[i]);
      p[i]:=0;
     end;
   end;
 end;
begin
   min:=maxlongint;
   readln(n);
   fillchar(a,sizeof(a),$7f);
   fillchar(p,sizeof(p),0);
   for i:=1 to n do
    for j:=1 to n do
     begin
      read(a[i,j]);
     end;
   f:=a;
   for i:=2 to n do
    read(t[i]);
   for k:=1 to n do
    for i:=1 to n do
     begin
     if i=k then continue;
     for j:=1 to n do
      begin
       if i=j then continue;
       if j=k then continue;
       f[i,j]:=min1(f[i,j],f[i,k]+f[k,j]);
      end;
     end;
   dfs(1,1,0);
   if min=maxlongint then writeln(-1) else writeln(min);
end.


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值