noip2005过河-题解

描述

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

题目给出独木桥的长度L,青蛙跳跃的距离范围S,T,桥上石子的位置。你的任务是确定青蛙要想过河,最少需要踩到的石子数。

对于30%的数据,L <= 10000;

对于全部的数据,L <= 10^9。

1 <= S <= T <= 10,1 <= M <= 100

这个问题的朴素的做法复杂度可看做O(L),显然令人感到绝望。关键在于缩距,这当然是一个问题,需要仔细考虑。作为OIer,算法的简单正确胜过一切。所以,首先,对于距离得很近的石头,管都不管,让他继续保持,因为显然离得近的石头是很讨厌的。于是考虑离得较远的,多远呢,反正只有100个石头,就把这个距离设成1000吧。下面考虑距离大于1000的两个石头。

继续解决问题之前,考虑这个问题:一个青蛙到底能跳哪些长度。如果S=1,不管T多少,都能跳任何整数,那S=2呢?最后写了一个验证程序(验证1到m的数哪些不能用k和k+1组合出来):

把k=1到9带进去,最终发现k=9时,最大的不能表达的为71,那就说明不管S,T是多少,只要S<T,那么距离>=72的所有点都可以被跳到,不管他是100还是1000(很好理解,假如S=5,T=8,那么我就只用5和6这两个步长就够了,7,8我丢掉不用,这完全可以)。这是一个重要的结论。

到这里,可以先把S=T的情况给搞掉,因为只能固定步长跳很简单。然后再来考虑缩距。若两个石头编号为i-1和i,并且f[i-1]已经算出来了,那么i石头左边有多少空位有价值呢?最左只到T个。再往左的话,f[i]和以后位置的f计算就用不上了,成为废的,所以不需要。那么i-1石头右边要留多少个石头呢?只需要T-1个。因为更右边的位置其实是左边已经有的位置跳过来的,跳的过程中又没有踩石头,所以没有必要留他们。那么现在只需要让i-1右边的T-1个位置到i的左边T个位置这两者之间的距离>=72即可。这样的话,原来左边的每一个位置可以更新右边的每一个位置,缩了过后,左边的每一个位置也可以更新右边的每一个位置,那变换前后可以认为是等价的。因为我 并没有改变任何有用的f[]的值,甚至连更新的方式也没改变(因为转移的关系被完整保留下来了),我去掉的,只是不影响后续计算的废值。这样每两个石头之间的距离变成了<=(72+2*T)<=92,100个石头的话,眯着眼睛也能过。到这里了,可以回去把那个距离的阈值设小一点,把1000改成Q=100都行,这样复杂度O(M*Q*T),还不到100000,搞定。以下是一份可行的解,单点耗时不超过5ms。

#include <cstdio>
#include <algorithm>
#include <iostream>
#define inf 1000000000
using namespace std;
int L,s,t,m;
int a[105],f[10005];
int main() {
    scanf("%d%d%d%d",&L,&s,&t,&m);
    if (s==t) {
        int ans=0;
        for (int i=1;i<=m;i++) {
            scanf("%d",&a[i]);
            ans+=!(a[i]%s);
        }
        printf("%d\n",ans);
        return 0;
    }
    for (int i=1;i<=m;i++) scanf("%d",&a[i]);
    sort(a+1,a+1+m);
    int p;
    if (a[1]>=100) f[p=72+t]=1;
    else f[p=a[1]]=1;
    for (int i=2;i<=m;i++) {
        if (a[i]-a[i-1]>=100) f[p=p+72+2*t]=1;
        else f[p=p+a[i]-a[i-1]]=1;
    }
    if (L-a[m]>=100) p=p+72+t;
    else p=p+L-a[m];
    for (int i=1;i<=p;i++) {
        int b=inf;
        for (int j=s;j<=t;j++) {
            if (i-j>=0) b=min(b,f[i-j]);
        }
        if (b!=inf) f[i]+=b;
        else f[i]=inf;
    }
    int ans=inf;
    for (int i=p-t;i<=p;i++) ans=min(ans,f[i]);
    printf("%d\n",ans);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/dengping_ss/article/details/82927013