【算法競賽刷題模板11】區間dp
0.總結
Get to the points first. The article comes from LawsonAbs!
- 區間
dp
是常見的一種dp
題 - 使用區間
dp
常用的套路
1.固定套路
區間dp
的套路是固定的。
- step1.確定區間長度
len
,一般是len
屬於[0,n]
。按照從小到大的順序遍歷一次,這個作爲dp問題處理的階段。 - step2.然後接着確定區間的端點,又因爲區間長度已經確定好了,當知道了區間的左端點,區間的右端點就可以通過
j=i+len-1
計算出來 - step3.這一步就是針對具體問題進行一個具體的分析,同時做一個狀態的轉移即可。
- step4.再複雜一點兒的情況,可能給你的這個序列並不是一個線性的,而是一個環,那麼就可能需要將這個環斷成一個鏈,這個鏈是原來序列的2倍長。例如
1 2 3
是一個環,那麼我們就生成一個序列1 2 3 1 2 3
。以此來解決存在環的問題。
2.練習題
2.1 【洛谷】P1880 石子合併
這是一道任何書中講區間dp
都會涉及到的模板題。可AC的代碼如下:
#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
int n,minl,maxl,f1[300][300],f2[300][300],num[300];
int s[300];
inline int d(int i,int j){return s[j]-s[i-1];}
//轉移方程:f[i][j] = max(f[i][k]+f[k+1][j]+d[i][j];
int main()
{
scanf("%d",&n);
for(int i=1;i<=n+n;i++) //因爲是一個環,所以需要開到兩倍再枚舉分界線,最後肯定是最大的
{
scanf("%d",&num[i]);
num[i+n]=num[i];
s[i]=s[i-1]+num[i];
}
for(int p=1;p<n;p++)
{
for(int i=1,j=i+p;(j<n+n) && (i<n+n);i++,j=i+p)
{
f2[i][j]=999999999;
for(int k=i;k<j;k++)
{
f1[i][j] = max(f1[i][j], f1[i][k]+f1[k+1][j]+d(i,j));
f2[i][j] = min(f2[i][j], f2[i][k]+f2[k+1][j]+d(i,j));
}
}
}
minl=999999999;
for(int i=1;i<=n;i++)
{
maxl=max(maxl,f1[i][i+n-1]);
minl=min(minl,f2[i][i+n-1]);
}
printf("%d\n%d",minl,maxl);
return 0;
}
2.2 【洛谷】 P1063 能量項鍊
下面是我寫的代碼:
#include<iostream>
using namespace std;
const int N = 205;
//dp[i][j]表示左端點爲i,長度爲j時的最大值
//arr[i][0]表示爲頭,arr[i][1]表示爲尾
//left[i][j]表示區間[i,j] 取最大值時的左端點值; right[i][j]表示區間[i,j]取最大值是右端點的值
int dp[N][N],lef[N][N],righ[N][N],arr[N][2];
int n;
int main(){
cin >> n;
for(int i = 1;i<=n;i++){
cin >> arr[i][0];
arr[i-1][1] = arr[i][0] ;//上個珠子的尾等於次頭
}
arr[n][1] = arr[1][0] ;//重新計算第一顆珠子的尾
//預處理部分
for(int i = 1;i<=n;i++){
// cout << arr[i][0]<<","<< arr[i][1]<<"\n";
lef[i][i] = arr[i][0];
righ[i][i] = arr[i][1];
}
for(int i = 1;i<=n;i++){//斷鏈成環
arr[n+i][0] = arr[i][0];
arr[n+i][1] = arr[i][1];
lef[n+i][n+i] = lef[i][i];
righ[n+i][n+i] = righ[i][i];
}
//開始計算
for(int len = 2;len<=n;len++){//len的範圍在[2,n]
for(int i = 1;i<=2*n;i++){//區間的左端點
int y = i+len-1;//區間的右端點
for(int k = i;k<y && y<=2*n; k++){
int temp = lef[i][k] * righ[i][k] * righ[k+1][y] + dp[i][k-i+1] + dp[k+1][y-k];
if(dp[i][len] < temp){
dp[i][len] = temp;
lef[i][y] = lef[i][k];
righ[i][y] = righ[k+1][y];
}
}
}
}
int res = 0;
for(int i = 1;i<=n;i++){
res = max(res,dp[i][n]);
}
cout << res<<"\n";
}
雖然可AC,但是稍顯冗餘,其原因是,dp
數組設的不好,導致需要用到lef,righ
兩個數組,很明顯,這個是可以避免的。下面再給出一個精簡版的代碼:
#include <bits/stdc++.h>
using namespace std;
int f[405][405];
int n,a[205];
int main()
{
cin >> n;
for(int i=1;i<=n;i++) //***對環形問題的處理技巧***
{
cin >> a[i];
a[n+i]=a[i];
}
for(int i=2;i<=n+1;i++)
{
for(int l=1;l+i-1<=2*n;l++) //如果採取了上述策略,一定要將2*n個點都更新
{
int r=l+i-1;
for(int k=l+1;k<=l+i-2;k++)
f[l][r]=max(f[l][r],f[l][k]+f[k][r]+a[l]*a[k]*a[r]);
}
}
int res=0;
for (int i=1;i<=n;i++) res=max(res,f[i][n+i]);
cout << res;
return 0;
}