《过河(river)》
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
By sx349 二、过河(NOIP2005提高组)
【问题描述】
在河上有一座独木桥,一只青蛙想沿着独木桥从河的一侧跳到另一侧。
在桥上有一些石子,青蛙很讨厌踩在这些石子上。
由于桥的长度和青蛙一次跳过的距离都是正整数,我们可以把独木桥上青蛙可能到达的点看成数轴上的一串整点:
0,1,……,L(其中L是桥的长度)。
坐标为0的点表示桥的起点,坐标为L
的点表示桥的终点。
青蛙从桥的起点开始,不停的向终点方向跳跃。
一次跳跃的
距离是S到T之间的任意正整数(包括S,T)。
当青蛙跳到或跳过坐标为L的
点时,就算青蛙已经跳出了独木桥。
题目给出独木桥的长度L,青蛙跳跃的距离范围S,T,桥上石子的位置。
你
的任务是确定青蛙要想过河,最少需要踩到的石子数。
【输入文件】
每組输入第一行有一个正整数L(1 <= L <= 109),表示独木桥的长度。
第二行有三个正整数S,T,M,分别表示青蛙一次跳跃的最小距离,最大距离,及桥上石子的个数,其中1 <= S <= T <= 10,1 <= M <= 100。
第三行有M 个不同的正整数分别表示这M个石子在数轴上的位置(数据保证桥的起点和终点
处没有石子)。
所有相邻的整数之间用一个空格隔开。
【输出文件】
每组输出只包括一个整数,表示青蛙过河最少需要踩到的石子数。
【输入样例】
10
2 3 5
2 3 5 6 7
【输出样例】
2
【数据分析】
对于30%的数据,L <= 10000;
对于全部的数据,L <= 109。
【算法分析】
仔细阅读题目,就会发现本题是求最优解问题,可以考虑贪心、搜索与动态规划的算法。
一、贪心法
考虑如下的贪心策略:
1)假设青蛙处在第i个位置,当它要进行下一次跳跃时,我们肯定希望它能跳得最远。
因此,我们首先考虑让青蛙跳t格,再尝试t-1、t-2……直到s 格,并选择其中最远的,且不会跳到石头上的一格。
2)当必然跳到石头上时,选择最远的一格。
显然,这个贪心策略不能得到最优解,我们可以举出一个反例:
当长度L=10,石头位置m为6、7、8,s=2 t=4:
根据贪心算法我们得到的跳法是0-〉4-〉8(678均会踩到石头)—〉12(终止),最后结果为1。
而最优解为0—〉3(或2)-〉5-〉9-〉11(或12、13),结果为0。
二、搜索
假设我们从位置0(即桥头)开始,搜索所有到达桥尾的路,采取的搜索方式应为深度优先搜索。
显然,当这个算法需要遍历整张表时,时间复杂度极高,搜索的次数约为(t-s)^(L/t)次。
当取最大数据时,要达到10^108次,远远超过计算机的运算能力。
三、动态规划
排除了上述两种方法,我们再来考虑使用动态规划。
由于石子个数很少(100以内),所以考虑以石子分阶段的一维动规,时间复杂度是O(n2)。
最多也只有100×100的时间。
但是这样分状态就十分复杂。
因为石头的分布是没有任何规律,而且会有后效性。
然后考虑用每一格划分阶段,状态转移方程很容易写出,时间复杂度为O(m n)。
但是采用极限数据时,由于n太大,还是会严重的超时。
这样看来我们很难用最简单的动归或搜索来解决本题。
现在我们来考虑特殊的做法:
由于在109的桥上,只有100个石子,因此,两个石子间会有很长的空条,符合条件的空条可以被压缩或者忽略。
我们只考虑s<>t的情况(当s=t时,只需输出位置为t倍数的石子的数目即可),当某一段空条的长度超过某个数时,可以将该空条压缩。
那么怎么确定这个常数呢?
我们可以看到,当某个点与当前活动点的距离超过关于s与t的函数f(s, t)时,该点及其之后的所有的点,都必然可以到达。
所以,当两个石子间的距离超过f(s,t)时,可以将其中的距离压缩到f(s,t)以下。
那怎么求这个f(s,t)呢?我们考虑模拟的方式。
因为t的最大值为10,所以f(s,t)的范围应该在10^10=100的范围内。
从0开始,搜索出所有可以跳到的位置并标记,然后再整个搜索范围内,寻找长为t的一串标记。
如果出现这样的一串标记,说明这t个数全部可以跳到,在其后的所有数均是从这t个数中跳出的,所以也都可以跳到。
这样的话,该标记的第一个,即为我们要找的f(s, t)。
确定了f(s,t)后,我们就可以将所有的石子长度重新排列,然后就可以利用搜索或动态规划来做。
由于长度在极限数据下还是只减少到了100*100左右,所以此时利用搜索还是有不能AC的可能。
所以,我们还是利用动态规划的状态转移方程来做。
c[i]:=min{c[i-temp]+d[i]}(s<=i<=t)
边界条件是c[0]=0;d[i]表示转换后,在第i格有无石子。
当然,要仔细考虑在前s格,因为在实际跳的过程中,这几格是永远跳不到的,所以在初始化过程中要将这几格置为maxint。
【程序清单】
program river;
var
a,a1:array[0..101] of longint;
b:array[0..100] of boolean;
c,d:array[0..10000] of longint;
l,s,t,m,ans,low,i,j,k,temp:longint;
flag:boolean;
procedure init;
begin
assign(input, 'river.in ');
reset(input);
readln(l);
readln(s,t,m);
for i:=1 to m do
read(a[i]);
a[0]:=0;
a[m+1]:=l;
for i:=1 to m-1 do
for j:=i+1 to m do
if a[i]>a[j]
then
begin
temp:=a[i];
a[i]:=a[j];
a[j]:=temp;
end;
close(input);
end;
procedure work1;
begin
for i:=1 to m do
if a[i] mod s=0
then
inc(ans);
end;
procedure work2;
begin
fillchar(b,sizeof(b),false); b[0]:=true;
for i:=s to t do
begin
for j:=0 to 100 do
if b[j] then
begin
k:=1;
while k*i+j<=100 do
begin
b[k*i+j]:=true;
inc(k);
end;
end;
end;
for i:=1 to 100 do
begin
flag:=true;
for j:=0 to t-1 do
if not b[i+j] then
begin
flag:=false;
break;
end;
if flag then
begin
low:=i;
break;
end;
end;
if low<t then low:=t;
for i:=1 to m+1 do
a1[i]:=(a[i]-a[i-1]-low) mod low+a1[i-1]+low;
a:=a1;
for i:=1 to m do
d[a[i]]:=1;
l:=a[m+1];
for i:=1 to l+t-1 do
c[i]:=maxint;
for i:=1 to l+t-1 do
for j:=s to t do
if (i-j>=0) and (c[i]>c[i-j]+d[i]) then
c[i]:=c[i-j]+d[i];
ans:=maxint;
for i:=l to l+t-1 do
if ans>c[i] then
ans:=c[i];
end;
begin
init;
if s=t then work1
else work2;
assign(output,'river.out');
rewrite(output);
writeln(ans);
close(output);
end.
【心得体会】
本题一眼看去就可以看出是一道应用动态规划求解的问题,但是直接用动态规划却不可行,原因就在于其中的数据量过大。
在高级本中提到的餐巾问题也不
可用动态规划求解,是因为维数太大。
而前面一题中也由于主附件这一特殊关系,所以也不能对原始数据直接动态规划。
根据这个实例我们可以看出,除了要满足最优子结构和无后效性原则外,是否使用动态规划还需要考虑,动态规划在处理这个问题时,所采用的“空间换时间”原则,是否能够使空间和时间均在合理范围之内。
同时,过河一题还给我们提出一个新的构想:线性动态规划中,出现了如题中那种元素数目大大小于空间总数的情况时,我们应该怎样解决?这种情况很容易让人想到稀疏矩阵的情况。
当我们在数组中不断地计算时,其实我们采用的是类似直接模拟的算法。
我们可以考虑将数组中稀疏的元素以表的形式存储,同时找出数据之间的关系,相当于将模拟转变为递推、甚至可以找出数学方法,问题就可以获得解决。
再回到“过河”,由于条件的特殊性(石子数目小于100),所以有一个较为特别的算法。
当石子数目逐渐上升时,由于最终L的数目巨大,所以还是不能直接进行动态规划。
对于这种情况下的过河问题,还有待我们对进一步研究。