NOIP 2015 过河(缩点+DP)

题目描述

在河上有一座独木桥,一只青蛙想沿着独木桥从河的一侧跳到另一侧。在桥上有一些石子,青蛙很讨厌踩在这些石子上。由于桥的长度和青蛙一次跳过的距离都是正整数,我们可以把独木桥上青蛙可能到达的点看成数轴上的一串整点: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个石子在数轴上的位置(数据保证桥的起点和终点处没有石子)。所有相邻的整数之间用一个空格隔开。 


数据规模: 
对于30%的数据,L<=10000; 
对于全部的数据,L<=1e9。 

 

输出

每组输出只包括一个整数,表示青蛙过河最少需要踩到的石子数。 

 

分析:这题的数据范围是1e9,很明显数组是存不下的,所以首先需要离散化,最方便的离散化方法就是缩点,这里用的是2520缩点,因为 lcm(1,2,3,4,5,6,7,8,9,10)= 2520,那么从一个点出发,无论青蛙的跳跃距离是多少,它都一定可以到达下标为2520处,所以当在2520处没有石头的时候,可以将当前点向后移2520或者将后面的点向前移2520。然后这题很明显是一道DP的题,设dp[i]为在i点出所踩的最少石子数,那么在这点前面的 [i - s,i - t]这个范围内的点很明显都可能改变 i 点的值,因此需要选择dp[ i - s] 到 dp[ i - t]这个范围里的最小值来转移,如果 i 这个位置有石头就再让 dp[i] + 1,没有石头就不加,所以在dp之前我们也需要处理出缩点之后哪些位置上有石头。还有一个需要注意的地方是,因为题上说越过L也算作过桥,所以DP的终点不是一个点,而是一个范围。

#include<map>
#include<set>
#include<cmath>
#include<queue>
#include<stack>
#include<cstdio>
#include<vector>
#include<cctype>
#include<cstring>
#include<utility>
#include<cstdlib>
#include<iomanip>
#include<iostream>
#include<algorithm>
#define Clear(x) memset(x,0,sizeof(x))
#define fup(i,a,b) for(int i=a;i<b;i++)
#define rfup(i,a,b) for(int i=a;i<=b;i++)
#define fdn(i,a,b) for(int i=a;i>b;i--)
#define rfdn(i,a,b) for(int i=a;i>=b;i--)
typedef long long ll;
using namespace std;
const int maxn = 1e6+7;
const int maxm = 1e2+27;
const int inf = 0x3f3f3f3f;
const double pi=acos(-1.0);
const double eps = 1e-8;
int dp[maxn],vis[maxn],a[maxm],road[maxm];
int L,S,T,M,ans;

int read()
{
    char ch=getchar();int ret=0,f=1;
    while(ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){ret=ret*10+ch-'0';ch=getchar();}
    return f*ret;
}

void slove()
{
    rfup(i,1,M)
        road[i]=(a[i]-a[i-1])%2520;//计算每两个石头之间的相对距离
    rfup(i,1,M){
       a[i]=a[i-1]+road[i];//根据相对距离计算出每个石头缩点后所在位置
       vis[a[i]]=1;//此处有石头
    }
    L=a[M];//终点的左边界
    fup(i,1,L+10){//DP的终点是一个范围,所以L后面的也需要转移
        dp[i]=M;
        rfup(j,S,T){
            if(i>=j) dp[i]=min(dp[i],dp[i-j]);
            dp[i]+=vis[i];
        }
    }
    ans=M;
    fup(i,L,L+10) ans=min(ans,dp[i]);
    printf("%d\n",ans);
    return;
}

int main()
{
    L=read(),S=read(),T=read(),M=read();
    rfup(i,1,M) a[i]=read();
    sort(a+1,a+1+M);//读入的石头不一定是排好序的,所以先排个序
    if(S==T){//如果S==T,则不存在状态转移,只有一种跳法
        ans=0;
        rfup(i,1,M) ans+=!(a[i]%S);
        printf("%d\n",ans);
        return 0;
    }
    slove();
    return 0;
}

 

下面再给出看到的另一个大神的缩点方法及其证明:

https://www.luogu.org/blog/littlejuruo/solution-p1052

蒟蒻表示根本看不懂。。。所以这种方法用到的是90点缩,但是需要注意,对于 s=t这种特殊情况,这种方法是不成立的,应为这种情况下,每次走的步数是固定的,因此这种情况需要特殊处理。

#include<map>
#include<set>
#include<cmath>
#include<queue>
#include<stack>
#include<cstdio>
#include<vector>
#include<cctype>
#include<cstring>
#include<utility>
#include<cstdlib>
#include<iomanip>
#include<iostream>
#include<algorithm>
#define Clear(x) memset(x,0,sizeof(x))
#define fup(i,a,b) for(int i=a;i<b;i++)
#define rfup(i,a,b) for(int i=a;i<=b;i++)
#define fdn(i,a,b) for(int i=a;i>b;i--)
#define rfdn(i,a,b) for(int i=a;i>=b;i--)
typedef long long ll;
using namespace std;
const int maxn = 1e4+7;
const int maxm = 1e2+27;
const int inf = 0x3f3f3f3f;
const double pi=acos(-1.0);
const double eps = 1e-8;
int dp[maxn],vis[maxn],a[maxm],road[maxm];
int L,S,T,M,ans;

int read()
{
    char ch=getchar();int ret=0,f=1;
    while(ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){ret=ret*10+ch-'0';ch=getchar();}
    return f*ret;
}

void slove()
{
    rfup(i,1,M){
        road[i]=a[i]-a[i-1];
        if(road[i]>90) road[i]=90;
    }
    road[M+1]=min(L-a[M],100);
    L=0;
    rfup(i,1,M){
        L+=road[i];
        vis[L]=1;//此处有石头
    }
    L+=road[M+1];
    rfup(i,1,L+9){
        dp[i]=inf;
        rfup(j,S,T){
            if(i>=j) dp[i]=min(dp[i],dp[i-j]+vis[i]);
        }
    }
    ans=inf;
    rfup(i,L,L+9) ans=min(ans,dp[i]);
    printf("%d\n",ans);
    return;
}

int main()
{
    L=read(),S=read(),T=read(),M=read();
    rfup(i,1,M) a[i]=read();
    sort(a+1,a+1+M);
    if(S==T){
        ans=0;
        rfup(i,1,M) ans+=!(a[i]%S);
        printf("%d\n",ans);
        return 0;
    }
    slove();
    return 0;
}

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值