前向星 V2.0——以及USACO3.2.6 Sweet Butter
以前我曾经学习过前向星,充分感觉到了他的好用以及编程复杂度小的优点。鉴于好久没有写程序,前向星也忘得差不多了,今天重新写篇博文来谈谈前向星的问题(耐心看,今天说的前向星有些不一样)
先介绍一下前向星(不是星星啊!),前向星是一个边集数组,把边的起点终点和权值存起来,然后以起点从小到大或者从大到小排序,记录每个顶点在数组中的起始位置和长度...以达到节省空间的目的。
代码就自己随便找找吧,有的是!
咱今天重点谈的的确是前向星,此前向星非彼前向星,我就不卖关子了,咱直接说吧:前向星升级版——链式前向星
(来俩沙茶把这人抬走)。
这个东西是从Malash大犇那里学来的,非常NB的东西哦~!
普通前向星好是好,可是如果数据规模大,排序他也不是O(1)时间,也许你排个序就把时间耽误了,不合适,这时候
用邻接矩阵存不下,你还不会写邻接链表,怎么办呢?链式前向星!
先介绍一下链式前向星的组成:
HEAD数组:记录每个顶点的起点位置
E数组:记录每条边的终点
NEXT数组:记录下一条边的位置
其他的诸如权值什么的,自己往上加,这三个数组是必须的。
拿Malash神牛的课件咱来举例说明一下链式前向星的实现
假设有向图边的输入顺序为:
1 2
2 3
3 4
1 3
4 1
1 5
4 5
那么我们来WATCH一下三个数组的状态(我去现写个程序 你们等会)
head[1]=6 head[2]=2 head[3]=3 head[4]=7 head[5]=0
e[1]=2 e[2]=3 e[3]=4 e[4]=3 e[5]=1 e[6]=5 e[7]=5
next[1]=0 next[2]=0 next[3]=0 next[4]=1 next[5]=0 next[6]=4 next[7]=5
恩,大致看懂了吧,这就是链式前向星的存储方式,下面讲一下查询方法
假设你要查与1相连的边,那么你先访问head[1]=6 根据e[6]你得到了边1 5
继续访问next[6]=4,得到了边1 3,继续访问next[4]=1,这时候你得到了边1 2,next[1]=0,访问结束.
嗯,SO EASY!如果你想访问第N条边,你可以加个计数器,想怎么发挥就随意咯~
我们来简单的代码实现一下
procedure insertedge(a,b,i:longint); //将边(a,b)插入到第I个位置 begin next[i]:=head[a]; //记录NEXT[I] e[i]:=b; //记录终点 head[a]:=i; //将HEAD[A]替换成新的 end;
下面是查询操作,以输出与定点V连接的点为例
procedure print(v:longint); //过程名随意改,你改成main什么的都行 var i:longint; begin i:=head[v]; while i <> 0 do begin write(e[i],' '); //在这里想对边干什么都可以...注意分寸= = i:=next[i]; end; end;
下面来介绍一下无向边存储,其实很简单,最简单的方法是把刚才插入有向边的过程调用两遍
procedure insertundirectededge(a,b,i:longint); //将第I条无向边加入前向星数组 begin insertedge(a,b,i shl 1-1); //i shl 1 = i << 1 = i*2 insertedge(b,a,i shl 1);//这就是猥琐地调用给过的程序的方法= =BS我吧- - end;
这个就是前向星V2.0------链式前向星!最大的好处就是不用排序了,拿省下来的排序时间去买零食去吧!
(莫拍砖)
下面介绍一下链式前向星的简单应用,以USACO3.2.6 Sweet Butter 为例
题干:http://www.nocow.cn/index.php/Translate:USACO/butter (你去看鸟文也是可以的)
这道题乍一看及其闹心,一堆牛来回走,牧场的牛总是动态变化的,也没法算啊...(至少我一开始的时候
是这样想的,不要BS) 仔细一分析,把牛一波一波的分开算,假设你就求第一个牧场里的牛到各个点的时间,
那么这时候你假设其他牧场都没有牛,其他点亦是如此,然后把各个牧场的牛到同一牧场花费的时间累加,
那么这道题就清晰地成为了多源最短路的问题,可以方便的用SPFA进行求解。
既然已经知道了链式前向星,那么就以它为实例吧,下面是我的程序,诸位大牛莫BS。
program butter;//上面的内容略掉
var n,p,r,ans:longint;
k,sum,dis,head:array[0..800] of longint;//k存的各牧场奶牛数,sum存所有牛到牧场的总时间
vis:array[0..800] of boolean;
e,next,w:array[0..2900] of longint;
q:array[0..100000] of integer;//队列,以防万一,开大点
procedure insertedge(a,b,i,v:longint);
begin
next[i]:=head[a];
e[i]:=b;
w[i]:=v;
head[a]:=i;
end;
procedure pre;
var i,t,x,y,z:longint;
begin
readln(n,p,r);
fillchar(k,sizeof(k),0);
fillchar(head,sizeof(head),0);
fillchar(next,sizeof(next),0);
for i:=1 to n do
begin
readln(t);
inc(k[t]);
end;
for i:=1 to r do
begin
readln(x,y,z);
insertedge(x,y,i shl 1-1,z);
insertedge(y,x,i shl 1,z);
end;
fillchar(sum,sizeof(sum),0);
ans:=maxlongint;
end;
procedure spfa(s:longint);
var hd,tl,now,cnt,i:longint;
begin
fillchar(vis,sizeof(vis),false);
for i:=1 to p do dis[i]:=maxlongint;
dis[s]:=0;
q[1]:=s;
hd:=0;tl:=1;
vis[s]:=true;
cnt:=1;
while cnt <> 0 do
begin
hd:=hd mod 100000+1;
now:=q[hd];
dec(cnt);
i:=head[now];
while i <> 0 do
begin
if dis[e[i]] > dis[now]+w[i]*k[s] then//w[i]*k[s]表示这些牛花的总时间
begin
dis[e[i]]:=dis[now]+w[i]*k[s];
if not vis[e[i]] then
begin
vis[e[i]]:=true;
tl:=tl mod 100000+1;
q[tl]:=e[i];
inc(cnt);
end;
end;
i:=next[i];
end;
vis[now]:=false;
end;
end;
procedure sol;
var i,j:longint;
begin
for i:=1 to p do
if k[i] <> 0 then
begin
spfa(i);
for j:=1 to p do
sum[j]:=sum[j]+dis[j]; //进行叠加
end;
for i:=1 to p do
if ans > sum[i] then ans:=sum[i]; //不解释了
end;
procedure outprint;
begin
writeln(ans);
end;
procedure main;
begin
pre;
sol;
outprint;
end;
begin
assign(input,'butter.in');reset(input);
assign(output,'butter.out');rewrite(output);
main;
close(input);close(output);
end.
Executing...
Test 1: TEST OK [0.000 secs, 516 KB]
Test 2: TEST OK [0.000 secs, 516 KB]
Test 3: TEST OK [0.000 secs, 516 KB]
Test 4: TEST OK [0.000 secs, 516 KB]
Test 5: TEST OK [0.000 secs, 516 KB]
Test 6: TEST OK [0.000 secs, 516 KB]
Test 7: TEST OK [0.027 secs, 516 KB]
Test 8: TEST OK [0.054 secs, 516 KB]
Test 9: TEST OK [0.108 secs, 516 KB]
Test 10: TEST OK [0.162 secs, 516 KB]
我其实开始不是用SUPER前向星写的,而是用类似邻接矩阵的存储方式 时间效率差不多可是空间是2360KB,这个是
那个的四分之一,可见是非常省空间的!
浙公网安备 33010602011771号