noip2005 过河 解题报告

过河 解题报告

【问题描述】

     在河上有一座独木桥,一只青蛙想沿着独木桥从河的一侧跳到另一侧。在桥上有一些石子,青蛙很讨厌踩在这些石子上。由于桥的长度和青蛙一次跳过的距离都是正整数,我们可以把独木桥上青蛙可能到达的点看成数轴上的一串整点:0,1,……,L(其中L是桥的长度)。坐标为0的点表示桥的起点,坐标为L的点表示桥的终点。青蛙从桥的起点开始,不停的向终点方向跳跃。一次跳跃的距离是S到T之间的任意正整数(包括S,T)。当青蛙跳到或跳过坐标为L的点时,就算青蛙已经跳出了独木桥。

          题目给出独木桥的长度L,青蛙跳跃的距离范围S,T,桥上石子的位置。你的任务是确定青蛙要想过河,最少需要踩到的石子数。其中L<=10^9,石子数m<=1000,1<=s<=t<=10;

【分析】

    这道题直接搜索显然是不可行的,必然TLE,考虑动态规划:

     opt[n]:=min{opt[n-i]+rock[n]}(s<=i<=t)  //rock[n]表示当前位置有没有石子。

    时间复杂度是O(n),对于小于等于10^9的L显然是不可行的,于是我们可以尝试进行优化。如此大的长度,而石子数却只有1000,说明中间必然有很大的空白,可以将其压缩。

    我们先看一个方程。px+(p+1)y=Q(采用跳跃距离p和p+1时跳至位置Q)因为p和p+1相隔1,所以当Q>=p(p-1)时该方程必然有整数解(可以用同余的思想证明:p mod (p+1)=-1;设Q mod (p+1)=-k (k>0) ,则当x=k时,(p+1)y=Q-px,mod (p+1)必然为0,此时y为非负整数。命题得证)。这就意味着如果跳跃距离如果不小于s(s-1),且s不等于t,那么必然可以跳到,这样便可以实现压缩,以石子为状态动态规划。

    好了,切入正题,说怎么做吧。我们用b[i]为能否用s到t的一次跳跃距离跳至i远的标志(当s<=i<=th或i=0时为真),c[v]为青蛙能否跳到相对距离为v远的标志(v≤90)。

      则当v<0时,c[v]:=0;

      当v>=s(s-1)时,c[v]:=true;

      当1<=s<=s(s-1)时,c[v]:=b[v];

    (s=t时需特殊处理)

    

    a[i,j]为青蛙跳至x[i]左方相对距离为j的位置时所经过的最少石子总数。若j=0,说明青蛙踩到了桥上的第i个石子。初始时,a[i,j]=n+1(0≤i≤n,0≤j≤t-1)。 第1种情况:x[i]-j位置位于x[i-1]的左方,即x[i]-j≤x[i-1]显然,跳至x[i]-j位置经过的最少石子总数为a[i-1,j-x[i]+x[i-1]]。第2种情况:x[i]-j位置位于x[i-1]的右方,即x[i]-j>x[i-1]。显然,如果青蛙能够由x[i-1]-v位置跳至x[i]-j位置(can(x[i]-j-x[i-1]+v)=true),则跳至x[i]-j位置经过的石子总数为a[i-1,v]或者为a[i-1,v]+1(j=0时,即踩到了桥上的第i个石子)。但究竟v多大时,才能使得最少石子数最少呢,我们无法预知,只能在0‥t-1的范围内一一枚举v,从中找出经过的最少石子数。 最后,我们枚举青蛙跳出独木桥前的最后一个起跳位置x[n]-i(0≤i≤t-1),从中计算出青蛙过河最少需要踩到的石子数。 

     至于s=t的情况是不能用这种方法处理的,因此要特殊处理,模拟一遍就可以了。

【代码】

     

View Code
 1 var i,j,n,m,k,l,v:longint;
2 x:array[0..100]of longint;
3 a:array[0..100,0..100]of longint;
4 b:array[0..100]of boolean;
5 s,t,len:longint;
6 function can(x:longint):boolean;
7 begin
8 if x<0 then exit(false);
9 if x>=s*(s-1) then exit(true) else exit(b[x]);
10 end;
11 begin
12 assign(input,'river.in');reset(input);
13 assign(output,'river.out');rewrite(output);
14 readln(len);readln(s,t,n);
15 for i:=1 to n do read(x[i]);
16 for i:=1 to n do
17 for j:=i+1 to n do
18 if x[i]>x[j] then
19 begin
20 k:=x[i];x[i]:=x[j];x[j]:=k;
21 end;
22 while x[n]>len do dec(n);
23 x[0]:=0;
24 fillchar(b,sizeof(b),false);
25 b[0]:=true;
26 for i:=s to 90 do
27 for j:=s to t do
28 b[i]:=b[i]or b[i-j];
29
30 if s=t then
31 begin
32 k:=0;
33 for i:=1 to n do
34 if x[i]mod s=0 then inc(k);
35 write(K);
36 close(input);close(output);
37 halt;
38 end;
39
40 fillchar(a,sizeof(a),63);
41 a[0,0]:=0;
42 for i:=1 to n do
43 for j:=0 to t-1 do
44 begin
45 if x[i]-j<=x[i-1] then
46 begin
47 if a[i,j]>a[i-1,j-(x[i]-x[i-1])] then
48 a[i,j]:=a[i-1,j-(x[i]-x[i-1])];
49 end else
50 begin
51 for v:=0 to t-1 do
52 if (can(x[i]-j-x[i-1]+v))and(a[i,j]>a[i-1,v]) then
53 a[i,j]:=a[i-1,v];
54 if j=0 then inc(a[i,j]);
55 end;
56 end;
57 k:=maxlongint;
58 for i:=0 to t-1 do if a[n,i]<k then k:=a[n,i];
59 writeln(k);
60 close(input);close(output);
61 end.

 

    

转载于:https://www.cnblogs.com/N-C-Derek/archive/2011/10/27/2226991.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值