1002


 描述 Description
    在河上有一座独木桥,一只青蛙想沿着独木桥从河的一侧跳到另一侧。在桥上有一些石子,青蛙很讨厌踩在这些石子上。由于桥的长度和青蛙一次跳过的距离都是正整数,我们可以把独木桥上青蛙可能到达的点看成数轴上的一串整点:0,1,……,L(其中L是桥的长度)。坐标为0的点表示桥的起点,坐标为L的点表示桥的终点。青蛙从桥的起点开始,不停的向终点方向跳跃。一次跳跃的距离是S到T之间的任意正整数(包括S,T)。当青蛙跳到或跳过坐标为L的点时,就算青蛙已经跳出了独木桥。 
   题目给出独木桥的长度L,青蛙跳跃的距离范围S,T,桥上石子的位置。你的任务是确定青蛙要想过河,最少需要踩到的石子数。 
   对于30%的数据,L <= 10000; 
   对于全部的数据,L <= 10^9。 
 输入格式 Input Format
    输入的第一行有一个正整数L(1 <= L <= 10^9),表示独木桥的长度。第二行有三个正整数S,T,M,分别表示青蛙一次跳跃的最小距离,最大距离,及桥上石子的个数,其中1 <= S <= T <= 10,1 <= M <= 100。第三行有M个不同的正整数分别表示这M个石子在数轴上的位置(数据保证桥的起点和终点处没有石子)。所有相邻的整数之间用一个空格隔开。 
 输出格式 Output Format

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


解析:本题目是自己碰到的第一个动态规划题目,遇到的难度也是前所未有。自己只能窘迫的说水平太低了,连续提交了十几遍才通过,这还是在参考别人代码的基础上AC的。顺别吐槽一下,Vijos的系统也实在太差了,自动删除回车换行打乱格式导致编译通不过,最后还逼着我换了firefox才可以顺利提交。

思路一:也是自己最开始的思路,我想到了递归求解此问题。虽然对于样例很容易的通过,但是提交之后10组测试数据,没有一对是正确的,各种窘迫,考虑到数据规模还以为是自己的数据类型定义的太小,还专门换成了long类型,但是提交之后仍然出错,这是关注错误信息,发现给出的是堆栈溢出,这时候就是傻子也会想到应该是递归层次太多导致的,看到那个L的范围,溢出也是理所应当的。不得不放弃这种思路,但是自己有不会别的思路,只能参考网友上各种牛们提供的代码。下面是自己的递归代码,还是后者脸皮贴出来吧。

#include 
#include 
#include 
 
 int NumStone = 101;
 int IsStone(int destinaton,unsigned int Stone[],int M)
 {
 	int high = M -1;
 	int low = 0;
     while(low <= high)
 	{
 		int mid = (low + high) / 2;
 		if(destinaton == Stone[mid])
 			return mid+1;
 		if (destinaton =0 && temp= L)
 	{
 		if(num < NumStone)
 		{
 			NumStone = num;
 		}
 	}
 	else
 	{
 		for(swap=S;swap<=T;swap++)
 		{
 			if (IsStone(location,Stone,M))
 			{
 				num++;
 			}
 			cal(location+swap,num,L,S,T,Stone,M);
 		}
 	}
 	
 }
 int main()
 {
 	unsigned int L;
 	int S,T,M,Temp,n;
 	scanf("%ld",&L);
 	scanf("%d %d %d",&S,&T,&M);
 	unsigned int *Stone = (unsigned int *)malloc(sizeof(unsigned int)*M);
 	for (n=0;n


思路二:这是人家正统的思想分析,我就直接贴过来吧,代码也是根据理解写的。方法为动态规划+路径压缩

本题是一类很有意思的动态规划题;不同于决策优化,本题要求优化状态,这就使题目增加了很多的灵活性。 
  朴素的动态规划注意到当前状态只与其前面的T个状态有关;所以说采用滚筒法可以在O(LT)的时间内解决本题。但是,本题中L非常大,因此我们希望算法的时间复杂度与L无关。

 
  一种想法是:将当前状态s与其前面的T个状态看作一个长度为T+1的状态数组,如果在一次滚筒更新中新旧两个数组完全一样,则在遇到下一块石头之前,状态将会完全不变。这个原则是很简单的,因为除非遇到一块石头,否则每一个决策的前提都是不变的,所以说滚筒更新下去,状态一定不变。 
  那么,我们就需要问一个问题:究竟会不会出现这种更新前后状态不变的情况呢?如果不会出现这些情况,那么算法的优化也就无从谈起了。事实上,只要S < T,就一定会出现这种情况。
  这是很好理解的。假设S < T,则青蛙可以跳T步或T-1步,这两个步长是互质的。根据扩展的欧几里德定理,当路程足够长的时候,一定会出现这样一种情况:前后T步全部被同一个数覆盖;这就可以直接应用优化了。

时间复杂度是O(NT)。 从桥的一侧到另一侧,中间最多只有100个石子。假设桥长为最大值(10^9),石头数也为最大值(100),则中间一定会有很多“空长条” (两个石子中的空地),关键是如何在处理时把这些“空长条”跳过,使得运算次数降到M次。

结论:
 若(采用跳跃距离p和p+1时可以跳至任何位置Q),则在Q≥P*(P-1)时是一定有解的。
 Because证明
 由于p与p+1间隔1,故方程px+(p+1)y=Q有整数解,设其解为
  x=x0+(p+1)t,y=y0-pt(t是整数)
 取适当的t,使得0≤x≤p(只需在x0上加上或减去若干个p+1),则当Q>p(p-1)-1时,有
 (p+1)y=Q-px>p(p-1)-1-px≥p(p-1)-1-p*p=-(p+1)
 于是y>-1,故y≥0也是非负整数。证毕.
 由于题目给出的一个区间是1≤S≤T≤10,于是当相邻的两个石子之间的距离不小于9*10=90时,则后面的距离都可以到达,我们就可以认为它们之间的距离就是90。如此一来,我们就将原题L的范围缩小为了100*90=9000,动态规划算法完全可以承受了。但是当S=T时,上述等式是无法使用的,在这种情况下,只需要在所有石子中,统计出坐标是S倍数的石子个数就可以了. 

  当然,如果S = T,这个性质显然就不成立了,这种情况下我们可以特判。 
  如果青蛙能够跳得步数不是连续的,这种优化还可以用吗? 
  可以的!如果青蛙跳得步数中有两个数是互质的,则优化立即生效;否则,我们将所有的石头的位置除以步数的最大公约数(不能被最大公约数整除的显然不可能被跳到),对总长度也做类似的变化,就可以套用优化方法了。 
 评价:这是NOIP中第一道DP优化题,虽然其难度远不如NOI,但其灵活度也不小,需要仔细地考察状态转移方程的特征,利用状态空间的稀疏性来进行优化。尤其是当S = 
T时这种特殊情况的讨论极易被遗漏。写代码的时候仍然暴露出很多问题,包括头文件的理解混乱,字符长度书写错误,边界问题考虑不周等。

下面是写出的代码:

 

#include 
 #include 
 int main()
 {
 	long int L ,temp,k;
 	int S,T,M,i,j,min;
 	long stone[102],b[10000];
 	int Num[10000];
 	scanf("%ld",&L);
 	scanf("%d %d %d",&S,&T,&M);
 	for (i=0;istone[j+1])
 				{
 					temp = stone[j];
 					stone[j]=stone[j+1];
 					stone[j+1]=temp;
 				}
 		}
 		stone[M] = L;
 
 		if (stone[0]>90)
 		{
 			k = stone[0]-90;
 			for(i=0;i<=M;i++)
 				stone[i] -= k;
 		}
 		for (i=1;i<=M;i++)
 		{
 			if (stone[i]-stone[i-1]>90)
 			{
 				k = stone[i]-stone[i-1]-90;
 				for(j=i;j<=M;j++)
 					stone[j] -= k;
 			}
 		}
 
 
 		memset(Num,-1,sizeof(Num));
 		memset(b,0,sizeof(b));
 		//标记石头
 		for (i=0;i=0)
 					min = Num[j];
 			}
 			if (min != 101)
 				Num[i] = min+b[i];
 		}
 		min = 101;
 		for (i=stone[M];i

今天看到一个小规模的动态规划,取消了路径压缩的过程,作者的代码页比较好。

#include
#include
const int MAXN=100020;
int flag[MAXN];
int dp[MAXN];
int main()
{
    int L,s,t,n;
    int a;
    while(scanf("%d%d%d%d",&L,&s,&t,&n)!=EOF)
    {
        memset(flag,0,sizeof(flag));
        memset(dp,-1,sizeof(dp));//初始化,-1为不能到达的 
        //dp[i]表示到底 i  点需要经过的最少石子数,-1表示不能到达 
        for(int i=0;i=0&&dp[j]!=-1)//j 点能够跳到 
                {
                    if(dp[i]==-1)dp[i]=dp[j]+flag[i]; //第一次 直 接 给 值 
                    else if(dp[i]>dp[j]+flag[i]) dp[i]=dp[j]+flag[i];//找小的值 
                    
                }    
            }    
        }  
        int res=10000;
        for(int i=L;i<=L+t-1;i++)//L 到 L+t-1 中最小的非 -1 值 
        {
            if(dp[i]!=-1&&dp[i]




 



 

 
 

你可能感兴趣的:(动态规划,Vijos)