POJ2375 Cow Ski Area——求强连通分量

本文探讨了一种使用Tarjan算法解决滑雪场缆车安装问题的方法,通过构建图并应用Tarjan算法求出强连通分量,最终得出最少缆车数量以实现滑雪场所有区域联通。此外,还提供了详细的算法实现步骤和代码片段。

这道题,乍一看,大家会想到那个记忆化搜索的“滑雪”,但是,这两道题是完全不同的。这题题意有些难以理解:给出一块滑雪场的地形图,只能由一个点向相邻的点去滑,而且低处不能滑向高处。缆车是可以安装在任意两点间的,每个点也可以安装多部缆车,缆车可以使连接的两个相通。求最少的缆车数目使得所有格子之间都互相联通。

怎么做呢?强联通分量嘛!

Tarjan是伟大的,以Tarjan冠名的算法主要有三个:求LCA的Tarjan算法,求割点、割边的Tarjan算法以及求强连通分量的Tarjan算法。本题所采用的就是Tarjan算法求强连通分量。其实,由于本题的特殊性,也可以用Floodfill来求强连通分量,但是Tarjan算法显然更为普遍,具有普适性。

具体说说这题的过程:

1、读入并建图,如果这点的高度不比相邻点高度低,那么就从此点向相邻点连一条有向边

2、Tarjan算法求出所有的强连通分量。给出伪代码(c++语言的同学不要笑我啊):

Void Tarjan(int i);
{
	time<-time+1;
	dfn[i]<-time;low[i]<-time;
	将i的入栈标记记为true
	for (j=last[i],j<>0,j=pre[j])
		{
			k=other[j];
			if (k在栈中)
				{if (dfn[k]<low[i]) low[i]=dfn[k]}
			else if(k未被访问过)
				{
					Tarjan(k);
					if (low[k]<low[i]) low[i]=low[k];
				}
		}
	if (low[i]==dfn[i]) Deal(i);//Deal的含义是处理出所有与i同属一个强连通分量的点
}


3、对于每个强连通分量进行处理,累加每个强连通分量的入度和出度。

4、统计入度和出度为0的强连通分量的个数,选择大的输出(这个可以证明,请各位同学自己完成证明)

CODE

{$M 100000000}
Program POJ2375;//By_Poetshy
Const
	maxn=250005;
	ddx:Array[1..4]of Integer=(0,0,1,-1);
	ddy:Array[1..4]of Integer=(-1,1,0,0);
Var
	i,j,k,m,n,p,q,l,time,sum,all					:Longint;
	color,pre,other,last,stack,into,outo,dfn,low	:Array[0..maxn*10]of Longint;
	a												:Array[0..505,0..505]of Longint;
	had												:Array[0..505,0..505]of Boolean;
	v												:Array[0..maxn]of Boolean;

Procedure Deal(i:Longint);
var j,k:Longint;
begin
	inc(all);
	while i<>stack[sum]do
		begin
			v[stack[sum]]:=false;
			color[stack[sum]]:=all;
			dec(sum);
		end;
	color[i]:=all;
	dec(sum);
	v[i]:=false;
end;

Procedure Tarjan(i:Longint);
var j,k:Longint;
begin
	inc(time);inc(sum);stack[sum]:=i;
	dfn[i]:=time;low[i]:=time;
	j:=last[i];v[i]:=true;
	while j<>0 do
		begin
			k:=other[j];
			if dfn[k]=-1 then
				begin
					Tarjan(k);
					if low[i]>low[k] then low[i]:=low[k];
				end else if v[k] then if dfn[k]<low[i] then low[i]:=dfn[k];
			j:=pre[j];
		end;
	if low[i]=dfn[i] then Deal(i);
end;

BEGIN
	readln(n,m);k:=0;time:=0;
	fillchar(last,sizeof(last),0);
	fillchar(into,sizeof(into),0);
	fillchar(outo,sizeof(outo),0);
	fillchar(v,sizeof(v),0);
	for i:=1 to m do
		for j:=1 to n do
			read(a[i,j]);
	for i:=1 to m do
		for j:=1 to n do
			begin
				q:=(i-1)*n+j;
				for p:=1 to 4 do
					if (ddx[p]+i>0)and(ddy[p]+j>0)and(ddx[p]+i<=m)and(ddy[p]+j<=n)then
						if a[i,j]>=a[i+ddx[p],j+ddy[p]] then
							begin
								inc(k);l:=(i+ddx[p]-1)*n+j+ddy[p];
								pre[k]:=last[q];last[q]:=k;other[k]:=l;
							end;
			end;
	fillchar(dfn,sizeof(dfn),255);
	for i:=1 to n*m do if dfn[i]=-1 then Tarjan(i);
	if all=1 then
		begin
			writeln(0);
			halt;
		end;
	for i:=1 to n*m do
		begin
			j:=last[i];
			while j<>0 do
				begin
					k:=other[j];
					if color[i]<>color[k] then
						begin
							inc(into[color[k]]);
							inc(outo[color[i]]);
						end;
					j:=pre[j];
				end;
		end;
	p:=0;q:=0;
	for i:=1 to all do
		begin
			if outo[i]=0 then inc(q);
			if into[i]=0 then inc(p);
		end;
	if p>q then writeln(p) else writeln(q);
END.


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值