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 <stdio.h>
#include <stdlib.h>
#include <Windows.h>
 
 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 <Stone[mid])
 			high = mid - 1;
 		else
 			low = mid + 1;
 	}
 	return 0;
 }
 void select(unsigned int a[], int count)   /* 將石子出現的位置排序 */
 
 {
 
 	int i,j,temp;
 
 	for(i=0;i<count;i++)         
 
 	{
 
 		temp=a[i];
 
 		j=i-1;
 
 		while(j>=0 && temp<a[j])
 
 		{
 
 			a[j+1]=a[j];
 
 			j--;
 
 		}
 
 		a[j+1]=temp;
 
 	}
 
 }
 
 void cal(int location,int num,long int L,int S,int T,unsigned int Stone[],int M)
 {
 	int swap;
 	if(location >= 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<M;n++)
 		scanf("%u",&Stone[n]);
 	select(Stone,M);
 	cal(0,0,L,S,T,Stone,M);
 	printf("%d",NumStone);
 	system("pause");
 	return 0;
 }
 


思路二:這是人家正統的思想分析,我就直接貼過來吧,代碼也是根據理解寫的。方法爲動態規劃+路徑壓縮

本題是一類很有意思的動態規劃題;不同於決策優化,本題要求優化狀態,這就使題目增加了很多的靈活性。 
  樸素的動態規劃注意到當前狀態只與其前面的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 <stdio.h>
 #include <string.h>
 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;i<M;i++)
 		scanf("%ld",&stone[i]);
 
 
 	if (S!=T)
 	{
 		for (i=0;i<M-1;i++)
 		{
 			for (j=0;j<M-i-1;j++)
 				if(stone[j]>stone[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<M;i++)
 			b[stone[i]] = 1;
 
 
 		Num[0] = 0;
 
 		for (i=S;i<stone[M]+T;i++)
 		{
 			min = 101;
 			for (j=i-T;j<=i-S;j++)
 			{
 				if (Num[j]<min && Num[j]!=-1 && j>=0)
 					min = Num[j];
 			}
 			if (min != 101)
 				Num[i] = min+b[i];
 		}
 		min = 101;
 		for (i=stone[M];i<stone[M]+T;i++)
 		{
 			if (Num[i] < min && Num[i]!= -1)
 				min = Num[i];
 		}
 		printf("%d",min);
 	}
 	else
 	{
 		min = 0;
 		for (i=0;i<M;i++)
 		{
 			if (stone[i] % S == 0)
 				min++;
 		}
 		printf("%d",min);
 	}
 	return 0;
 }

今天看到一個小規模的動態規劃,取消了路徑壓縮的過程,作者的代碼頁比較好。

#include<stdio.h>
#include<string.h>
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<n;i++)
        {
            scanf("%d",&a);
            flag[a]=1;//有石子爲1,否則爲0 
        }    
        dp[0]=0;
        for(int i=s;i<=L+t-1;i++)
        {
            for(int j=i-t;j<=i-s;j++)// j 點跳到 i 點 
            {
                if(j>=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]<res) res=dp[i];
        }      
        printf("%d\n",res);
    }    
    return 0;
}




 



 

 
 

發佈了46 篇原創文章 · 獲贊 102 · 訪問量 47萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章