算法思想
- 概念:区间类动态规划是线性动态规划的扩展,它在分阶段地划分问题时,与阶段中元素出现的顺序和由前一阶段的哪些元素合并而来由很大的关系。令状态dp[i][j]表示将下标位置i到j的所有元素合并能获得的价值的最大值.
- 状态转移方程: (dp[i][j] = max{dp[i][k] + dp[k+1][j] + cost}),cost为将这两组元素合并起来的代价。
- 特征 :能将问题分解为能两两合并的形式
- 步骤:由于计算dp[i][j]的值时需要知道所有dp[i][k]和dp[k+1][j]的值,而这两个中包含的元素的数量都小于dp[i][j],所以我们以 len = j-i+1作为DP的阶段。首先从小到大枚举len,然后枚举i的值,根据len和i用公式计算出j的值,然后枚举中间点k,时间复杂度为O(n^3)
- 核心代码
MS(dp , 0);//清零
for(int len=2; len<=n; len++){ //枚举长度
for(int i=0; i<n; i++){//枚举起点
int j = i + len - 1;//计算终点
if(j >= n) break;//不可越界
if(conditon) state transition...//根据条件写状态转移方程
for(int k=i; k<j; k++){ //枚举中间点,合并
dp[i][j] = max(dp[i][j] , dp[i][k] + dp[k+1][j] + cost)
}
}
}
入门例题
int dp[maxn][maxn];
string s;
int n;
void solve(){
rep(len , 2 , n){
rep(i , 0, n - 1){
int j=i+len-1;
if(j >= n) break;
//cout<<s[i]<<": "<<s[j]<<endl;
if(s[i] == '(' && s[j] == ')' || s[i] == '[' && s[j] == ']') dp[i][j] = dp[i+1][j-1] + 2;
rep(k, i, j-1){
dp[i][j] = max(dp[i][j] , dp[i][k] + dp[k+1][j]);
}
}
}
}
int main(){
while(cin>>s){
if(s == "end") break;
n = s.length();
MS(dp , 0);
solve();
cout<<dp[0][n-1]<<endl;
}
return 0;
}
- Multiplication Puzzle POJ - 1651
- 一个序列,头尾不取,每次取掉一个数得分a[i]a[i-1]a[i+1],如何取中间的数字使得最后的得分最少
- 假设有一个区间[l,r],dp[i][j]表示i,j位置不取,最终可以得到的最小值,对于区间[l,r],最后一步必然是a[l],a[k],a[r]并且取走a[k],k是中间点且不是端点
- 假设dp[l,k] dp[k][r]已经算出来了,枚举K的位置,状态转移方程(dp[i][j] = min(dp[i][j] , dp[i][k] + dp[k][j] + a[i]*a[k]*a[j]);)
ll a[maxn];
ll dp[maxn][maxn];
void solve(){
rep(len , 3 , n){ //枚举区间长度,长度至少是3
rep(i, 1, n){ //枚举起点
int j=i+len-1; //得到终点
if(j > n) break; //检查越界
dp[i][j] = inf; //求最小值 先初始化inf
rep(k, i+1, j-1){ //枚举中间点
dp[i][j] = min(dp[i][j] , dp[i][k] + dp[k][j] + a[i]*a[k]*a[j]);
}
}
}
}
int main(){
while(sc(n) != EOF){
rep(i,1,n) scl(a[i]); //从1开始读数字
MS(dp,0);//初始化DP
solve();
cout<<dp[1][n]<<endl;
}
return 0;
}