參考博文:區間dp小結(附經典例題)
首先,什麼是區間dp?它是幹什麼的?
先在小區間進行dp得到最優解,然後再利用小區間的最優解合併求大區間的最優解以上。操作往往涉及到區間合併問題
模板如下:
//mst(dp,0) 初始化dp陣列
for(int i=1;i<=n;i++)
for(int
len=2;len
<=n;len++) //區間長度
for(int i=1;i<=n;i++) //列舉起點
}
注意區間的列舉起點。
例題1:51nod1021 石子合併
題意:
n堆石子擺成一條線。現要將石子有次序地合併成一堆。規定每次只能選相鄰的2堆石子合併成新的一堆,並將新的一堆石子數記為該次合併的代價。計算將n堆石子合併成一堆的最小代價。
首先,假如合併的次序沒有限制,那麼我們將每堆石子看成乙個葉結點,每次合併的代價為數中結點,採用貪心的策略,則整個合併過程就是乙個哈夫曼樹的建樹過程。
但是這裡合併的時候要求每次只能合併相鄰的兩堆,則貪心這裡就會出錯了,因此我們採用dp的思想進行求解。
我們用dp[i][j]來表示合併第i堆到第j堆石子的最小代價,那麼狀態轉移方程為:
dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]+w[i][j]);
其中w[i][j]表示把兩部分合併起來的代價,即從第i堆到第j堆石子個數的和,為了方便查詢,我們可以用sum[i]表示從第1堆到第i堆的石子個數和,那麼w[i][j]=sum[j]-sum[i-1].
**如下:
#include
#include
#include
#include
using
namespace
std;
#define mst(a,b) memset((a),(b),sizeof(a))
#define rush() int t;scanf("%d",&t);while(t--)
typedef
long
long ll;
const
int maxn = 205;
const ll mod = 1e9+7;
const ll inf = 1e18;
const
double eps = 1e-9;
int n,x;
int sum[maxn];
int dp[maxn][maxn];
int main()
for(int len=2;len<=n;len++)
for(int i=1;i<=n;i++)
} printf("%d\n",dp[1][n]);
} return
0;
}
當然有的時候直接這樣裸著o(n^3)會t,所以我們有平行四邊形優化版本:
由於狀態轉移時是三重迴圈的,我們想能否把其中一層優化呢?尤其是列舉分割點的那個,顯然我們用了大量的時間去尋找這個最優分割點,所以我們考慮把這個點找到後儲存下來
用s[i][j]表示區間[i,j]中的最優分割點,那麼第三重迴圈可以從[i,j-1)優化到【s[i][j-1],s[i+1][j]】。(這個時候小區間s[i][j-1]和s[i+1][j]的值已經求出來了,然後通過這個迴圈又可以得到s[i][j]的值)。
**如下:
#include
#include
#include
#include
using
namespace
std;
#define mst(a,b) memset((a),(b),sizeof(a))
#define rush() int t;scanf("%d",&t);while(t--)
typedef
long
long ll;
const
int maxn = 205;
const ll mod = 1e9+7;
const ll inf = 1e18;
const
double eps = 1e-9;
int n,x;
int sum[maxn];
int dp[maxn][maxn];
int s[maxn][maxn];
int main()
for(int len=2;len<=n;len++)
for(int i=1;i<=n;i++)
} }
printf("%d\n",dp[1][n]);
} return
0;
}
例題2:hdu3506 猴子派對
題意:
問題轉化後其實就是環形石子合併,即現在有圍成一圈的若干堆石子,其他條件跟其那面那題相同,問合併所需最小代價。
解法:
把前n-1堆石子乙個個移到第n個後面,那樣環就變成了線,即現在有2*n-1堆石子需要合併,我們只要求下面的式子即可。求法與上面那題完全一樣。
這道題在uestc版本上不做平行四邊形優化會t。
#include
#include
#include
using
namespace
std;
typedef
long
long ll;
const
int inf = 99999999;
const
int maxn = 10000;
int a[maxn];
int sum[maxn], dp[maxn][maxn], s[maxn][maxn];
int main()
for (int i = 1; i <= 2 * n; i++)
sum[i] = sum[i - 1] + a[i];
for (int m = 1; m <= n; ++m)
else }}
}}
int ans = inf;
for (int i = 1; i <= n; ++i)
printf("%d\n", ans);
}return
0;}
例題3:hdu5115 dire wolf
題意:
有一排狼,每只狼有乙個傷害a,還有乙個傷害b。殺死乙隻狼的時候,會受到這只狼的傷害a和這只狼兩邊的狼的傷害b的和。如果某位置的狼被殺,那麼殺它左邊的狼時就會收到來自右邊狼的b,因為這兩隻狼是相鄰的了。求殺掉一排狼的最小代價。
解法:
設dp[i][j]為消滅編號從i到j只狼的代價,那麼結果就是dp[1][n],列舉k作為最後乙隻被殺死的狼,此時會受到a[k]和b[i-1] b[j+1]的傷害,取最小的即可
可列出轉移方程:
dp[i][j]=min(dp[i][j], dp[i][k-1]+dp[k+1][j]+a[k]+b[i-1]+b[j+1]);
dp[i][i]=a[i]+b[i-1]+b[i+1];
**如下:
#include
#include
#include
#include
#include
using
namespace
std;
const
int n=220
const
int inf=0xfffffff
int main()
for(int l=0; l<=n; l++)//注意邊界}}
printf("case #%d: %d\n", t++, dp[1][n]);
}return
0;}
區間dp模板
板子題 n堆石子擺成一條線。現要將石子有次序地合併成一堆。規定每次只能選相鄰的2堆石子合併成新的一堆,並將新的一堆石子數記為該次合併的代價。計算將n堆石子合併成一堆的最小代價。例如 1 2 3 4,有不少合併方法 1 2 3 4 3 3 4 3 6 4 9 10 19 1 2 3 4 1 5 4 5...
理解區間dp和例題
一 講解 1 作用 用於不知道從 合併的動態規劃題,不比線性dp 2 解法步驟 即列舉區間長度,再列舉左端點,之後列舉區間的斷點進行轉移。3 核心思路 既然讓我求解在乙個區間上的最優解,那麼我把這個區間分割成乙個個小區間,求解每個小區間的最優解,再合併小區間得到大區間即可。所以在 實現上,我可以列舉...
石子歸併(區間 dp 模板)
有n堆石子排成一排,其中第i堆的石子的重量為ai,現要將石子有次序地合併成一堆。規定每次只能選相鄰的2堆合併成新的一堆,形成的新石子堆的重量以及消耗的體力都是兩堆石子的重量之和。求把全部n堆石子合併成一堆最少需要消耗多少體力。include include include include inclu...