C语言动态规划(7)___过河(Vijos P1002)

Problem Description

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

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

对于30%的数据,L <= 10000;
对于全部的数据,L <= 10^9。

Input
输入的第一行有一个正整数L(1 <= L <= 10^9),表示独木桥的长度。第二行有三个正整数S,T,M,分别表示青蛙一次跳跃的最小距离,最大距离,及桥上石子的个数,其中1 <= S <= T <= 10,1 <= M <= 100。第三行有M个不同的正整数分别表示这M个石子在数轴上的位置(数据保证桥的起点和终点处没有石子)。所有相邻的整数之间用一个空格隔开。
Output
输出只包括一个整数,表示青蛙过河最少需要踩到的石子数。
Sample Input
 
   
10
2 3 5
2 3 5 6 7
Sample Output
2
 
   
 
  


本来这道题可以说是一道简单的DP题,状态转移方程也容易找出:f [ i ] = min ( f [ i - j ] )  +  stone [ i ]  S <= j <=T && i >= j

但是,这道题的难点就是独木桥的长度 L 最高可达到 10^9.如果用常规方法用 for 循环从1递推到 L + T,难免会超时.

这个时候我们要用离散化思想来压缩路径.

我们会发现:

          f [ i ] 只跟 f [ (i - j)min ]~ f [ (i - j)max ]   ( S <= j <= T && i >= j)有关.

           石头的个数M远远小于L.换句话意思就是在某两个石头之间存在一大段空白,这个时候 f [ i ] 在这个区域递推值都是不变的.

由于上面的我们可知在递推 f [ step ] 的时候,我们最多需要f [ step - T ]~ f [ step - S ]之间的值.这个时候我们只要第一次推出某一个  step1 位置,使得:

                              f [ step1 - T ]~ f [ step1 - S ]  f [ step - T ]~ f [ step - S ] 每个值对应相等.(如果S≠T, f [ step1 - T ]~ f [ step1 - S ] 每个值也是相等的)

剩下的从 step1 step就不用递推了.这里我们就达到了优化的左右,路径长度从 step 优化到 step1.


现在我们只需要找到最大的 step1,然后就可以把 L 压缩到 step1*M.   

p*x+(p+1)*y=Q(采用跳跃距离 pp+1 时可以跳至任何位置 Q),则在Q ≥ P*(P-1)时是一定有解的。
由于题目给出的一个区间是1≤S≤T≤10,于是当相邻的两个石子之间的距离不小于8*9=72时,则后面的距离都可以到达,我们就可以认为它们之间的距离就是72。如此一来,我们就将原题L的范围缩小为了100*72=7200,动态规划算法完全可以承受了。

特殊的,S=T的时候,那么上式方程无恒解,而f [ step1 - T ]~ f [ step1 - S ] 之间的每个值并不是都想等的,但 f [ step1 - T ]~ f [ step1 - S ]  f [ step - T ]~ f [ step - S ] 每个值对应相等.所以对于这种情况我们不能简简单单的把两个石头之间的距离压缩成72.而是还要加上除以72的余数.


压缩策略: 那么对于每两个石头之间的距离 Xi.

         ① 当Xi<2*72的时候,不予压缩.

         ②当Xi≥2*72的时候,压缩Xi=72-(Xi)%72 ;

总规模最大为:2*100*72=14400.

#include  
#include  
#include  
using namespace std;  
int f[3000005];  
int stone[105];  
bool vis[3000005];  
  
int main()  
{  
    int L,S,T,M;
	int X[105];
    scanf("%d%d%d%d",&L,&S,&T,&M);  
    int i,j,ans=0;  
    for(i=1;i<=M;i++)  
        scanf("%d",&stone[i]);
	if(S==T)
	{
		for(i=1;i<=M;i++)
			if(stone[i]%S==0)
				ans++;
		printf("%d\n",ans);
		return 0;
	}
	stone[0]=0;  
    sort(stone+1,stone+M+1); 
    stone[M+1]=L;  
	for(i=1;i<=M;i++)
		X[i]=stone[i+1]-stone[i];
    for(i=1;i<=M;++i)  
    {  
        if((stone[i+1]-stone[i])>72)      
		{
			if(X[i]>72)
				stone[i+1]=stone[i]+(stone[i+1]-stone[i])%72+72;
			else
				stone[i+1]=stone[i]+(stone[i+1]-stone[i])%72;
		}
    }  
    L=stone[M+1];  
    for(i=1;i<=M;++i)  
        vis[stone[i]]=1;  
    f[0]=0;  
    for(i=1;i<=L+T;++i)  
    {  
        f[i]=M;  
        for(j=S;j<=T;++j)  
                if(i>=j && f[i]>f[i-j]+vis[i])  
                        f[i]=f[i-j]+vis[i];  
    }  
    ans=M;  
    for(i=L;i<=L+T;++i)  
        if(ans>f[i])  
            ans=f[i];  
  
    printf("%d\n",ans);  
    return 0;  
}  


你可能感兴趣的:(ACM_日常)