記憶化搜尋(搜尋+dp思想)
一:簡介
(1)記憶化搜尋 即 搜尋+動態規劃陣列記錄上一層計算結果,避免過多的重複計算
演算法上依然是搜尋的流程,但是搜尋到的一些解用動態規劃的那種思想和模式作一些儲存;一般說來,動態規劃總要遍歷所有的狀態,而搜尋可以排除一些無效狀態。更重要的是搜尋還可以剪枝,可能剪去大量不必要的狀態,因此在空間開銷上往往比動態規劃要低很多。
記憶化演算法在求解的時候還是按著自頂向下的順序,但是每求解一個狀態,就將它的解儲存下來,以後再次遇到這個狀態的時候,就不必重新求解了。
這種方法綜合了搜尋和動態規劃兩方面的優點,因而還是很有實用價值的。可以歸納為:記憶化搜尋=搜尋的形式+動態規劃的思想
(2)簡單例子
題目描述:已知n個slots,1<n<17,每個slot有一個height,height的值有四種,分別為{1,2,3,4}.給你n個slot的,必須滿足以下兩個條件,求有多少種情況. 一:必須有兩個相鄰的slot的差為3,即一個為4,一個為1. 二:必須有三種不同的height值.
Sample Input
2
3
-1
Sample Output
2: 0
3: 8
這道題有組合公式,但想推出來不容易,其實用搜索就能很好地解決。
如果用暴搜的話,當n>10就會超時。這時,很容易想到動態規劃,但DP不僅需要推出狀態轉移方程式,還要進行拓撲排序,對於這題,很難用傳統的DP解決。雖然不能使用傳統意義上的動態規劃解決本題,但動態規劃的思想仍然能起到作用
記憶化搜尋的實質是動態規劃,效率也和動態規劃接近,形式是搜尋,簡單直觀,程式碼也容易編寫,不需要進行什麼拓撲排序了。
對於狀態的儲存,可用陣列num[a] [b][c][d],其中a為還剩幾個,b為找到了哪幾個,c為找到的最後一個數,d表示是否已經出現1,4相連的兩數
二:案例詳解
(1)題目描述:給從左至右排好隊的小朋友們分糖果,
要求:1.每個小朋友都有一個得分,任意兩個相鄰的小朋友,得分較高的所得的糖果必須大於得分較低的,相等則不作要求。
2.每個小朋友至少獲得一個糖果。
求:至少需要的糖果數。
輸入:
輸入包含多組測試資料,每組測試資料由一個整數n(1<=n<=100000)開頭,接下去一行包含n個整數,代表每個小朋友的分數Si(1<=Si<=10000)。
輸出:
對於每組測試資料,輸出一個整數,代表至少需要的糖果數。
(2)解題報告
所有人的最小能分配到的糖果值就可以通過他的左右兩個人計算出來。可以採用記憶化搜尋演算法。複雜度是O(n);
input:
3
1 10 1
3
6 2 3
2
1 1
output:
4
5
2
(3)程式碼實現
#include<cstdio>
#include<cstring>
#define MAX 100001
#define max(a,b) (a)>(b)?(a):(b)
using namespace std;
int cal(int r,int n,int dp[],int Arr[])
{
if(dp[r]>0)
return dp[r];//已經計算過
dp[r]=1;
if(r+1<=n&&Arr[r]>Arr[r+1])//右邊有人比他小,要受右邊限制
dp[r]=max(dp[r],cal(r+1,n,dp,Arr)+1);
if(r-1>=1&&Arr[r]>Arr[r-1])//左邊有人比他小,要受左邊限制
dp[r]=max(dp[r],cal(r-1,n,dp,Arr)+1);
return dp[r];
}
int main(int argc,char *argv[])
{
int n;
int Arr[MAX];
int dp[MAX];
while(scanf("%d",&n)!=EOF)
{
int i;
memset(dp,0,sizeof(dp));
for(i=1;i<=n;i++)
scanf("%d",&Arr[i]);
long long sum=0;
for(i=1;i<=n;i++)
sum+=cal(i,n,dp,Arr);
printf("%lld\n",sum);
}
return 0;
}