动态规划的性质:
最优子结构性质:如果问题的最优解所包含的子问题的解也是最优的,我们就称该问题具有最优子结构性质(即满足最优化原理)。
子问题重叠性质:子问题重叠性质是指在用递归算法自顶向下对问题进行求解时,每次产生的子问题并不总是新问题,有些子问题会被重复计算多次。
无后效性:将各阶段按照一定的次序排列好之后,对于某个给定的阶段状态,它以前各阶段的状态无法直接影响它未来的决策,而只能通过当前的这个状态。换句话说,每个状态都是过去历史的一个完整总结。这就是无后向性,又称为无后效性。
区间DP
一种运用于区间计算的DP算法
主要思想:
先在小区间取得最优解,然后小区间合并时更新大区间的最优解
基本代码:
// dp[i][j] 表示以i为起点j为终点的区间的答案
for (int i = 1; i <= n; i++) { // 初始化dp数组
dp[i][i] = value[i];
}
for (int len = 2; len <= n; len++) { //枚举区间长度
for (int i = 1; i <= n; i++) { //枚举起点
int j = i + len - 1; //区间终点
if (j > n) break; //若越界则结束
for (int k = i; k < j; k++) { //枚举分割点
// 状态转移
dp[i][j] = max(dp[i][j], dp[i][k] + dp[k + 1][j] + cost[i]
}
}
}
return dp[1][n]; // 最终答案
例题
例题1 hrbust 1818 石子合并问题–直线版
一条直线上摆放着一行共n堆的石子。现要将石子有序地合并成一堆。规定每次只能选相邻的两堆合并成新的一堆,并将新的一堆石子数记为该次合并的得分。请编辑计算出将n堆石子合并成一堆的最小得分和将n堆石子合并成一堆的最大得分。
Input
输入有多组测试数据。
每组第一行为n(n<=100),表示有n堆石子,。
二行为n个用空格隔开的整数,依次表示这n堆石子的石子数量ai(0<ai<=100)
Output
每组测试数据输出有一行。输出将n堆石子合并成一堆的最小得分和将n堆石子合并成一堆的最大得分。 中间用空格分开。
Sample Input
3
1 2 3
Sample Output
9 11
思路:
设定 DP[i][j] 为区间从 i 到 j的最优合并解
其状态转移方程为
dp[i][i]=min/max(dp[i][j],dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1];
代码实现
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int N=105;
int stone[N];
int mdp[N][N];
int madp[N][N];
int main()
{
int n;
while(~scanf("%d",&n))
{
memset(mdp,0x3f,sizeof(mdp));
memset(madp,0,sizeof(madp));
memset(stone,0,sizeof(stone));
for(int i=1;i<=n;i++)
{
scanf("%d",&stone[i]);
stone[i]+=stone[i-1];
mdp[i][i]=0;
madp[i][i]=0;
}
for(int len=2;len<=n;len++)
{
for(int i=1;i<=n;i++)
{
int j=i+len-1;
if(j>n) break;
for(int k=i;k<j;k++)
{
int mtp=mdp[i][k]+mdp[k+1][j]+stone[j]-stone[i-1];
int matp=madp[i][k]+madp[k+1][j]+stone[j]-stone[i-1];
mdp[i][j]=min(mdp[i][j],mtp);
madp[i][j]=max(madp[i][j],matp);
}
}
}
printf("%d %d\n",mdp[1][n],madp[1][n]);
}
return 0;
}
例题2:hrbust 1819 石子合并问题–圆形版
在圆形操场上摆放着一行共n堆的石子。现要将石子有序地合并成一堆。规定每次只能选相邻的两堆合并成新的一堆,并将新的一堆石子数记为该次合并的得分。请编辑计算出将n堆石子合并成一堆的最小得分和将n堆石子合并成一堆的最大得分。
Input
输入有多组测试数据。
每组第一行为n(n<=100),表示有n堆石子,。
二行为n个用空格隔开的整数,依次表示这n堆石子的石子数量ai(0<ai<=100)
Output
每组测试数据输出有一行。输出将n堆石子合并成一堆的最小得分和将n堆石子合并成一堆的最大得分。 中间用空格分开。
Sample Input
3
1 2 3
Sample Output
9 11
思路:
先将数组倍增后,使用直线版做法求解即可
代码实现:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=210;
int stone[N];
int sum[N];
int mdp[N][N];
int madp[N][N];
int main()
{
int n;
while(~scanf("%d",&n))
{
memset(mdp,0x3f,sizeof(mdp));
memset(madp,0,sizeof(madp));
memset(stone,0,sizeof(stone));
for(int i=1;i<=n;i++)
{
scanf("%d",&stone[i]);
sum[i]=sum[i-1]+stone[i];
mdp[i][i]=0;
madp[i][i]=0;
}
for(int i=n+1;i<=2*n;i++)
{
sum[i]=sum[i-1]+stone[i-n];
mdp[i][i]=0;
madp[i][i]=0;
}
for(int len=2;len<=n;len++)
{
for(int i=1;i<=2*n;i++)
{
int j=i+len-1;
if(j>2*n) break;
for(int k=i;k<j;k++)
{
int mtp=mdp[i][k]+mdp[k+1][j]+sum[j]-sum[i-1];
int matp=madp[i][k]+madp[k+1][j]+sum[j]-sum[i-1];
mdp[i][j]=min(mdp[i][j],mtp);
madp[i][j]=max(madp[i][j],matp);
}
}
}
int ans1=1e9,ans2=0;
for(int i=1;i<=n;i++)
{
ans1=min(mdp[i][i+n-1],ans1);
ans2=max(madp[i][i+n-1],ans2);
}
printf("%d %d\n",ans1,ans2);
}
return 0;
}
例题三:括号匹配
We give the following inductive definition of a “regular brackets” sequence:
the empty sequence is a regular brackets sequence,
if s is a regular brackets sequence, then (s) and [s] are regular brackets sequences, and
if a and b are regular brackets sequences, then ab is a regular brackets sequence.
no other sequence is a regular brackets sequence
For instance, all of the following character sequences are regular brackets sequences:
(), [], (()), ()[], ()[()]
while the following character sequences are not:
(, ], )(, ([)], ([(]
Given a brackets sequence of characters a1a2 … an, your goal is to find the length of the longest regular brackets sequence that is a subsequence of s. That is, you wish to find the largest m such that for indices i1, i2, …, im where 1 ≤ i1 < i2 < … < im ≤ n, ai1ai2 … aim is a regular brackets sequence.
Given the initial sequence ([([]])], the longest regular brackets subsequence is [([])].
Input
The input test file will contain multiple test cases. Each input test case consists of a single line containing only the characters (, ), [, and ]; each input test will have length between 1 and 100, inclusive. The end-of-file is marked by a line containing the word “end” and should not be processed.
Output
For each input case, the program should print the length of the longest possible regular brackets subsequence on a single line.
Sample Input
((()))
()()()
([]])
)[)(
([][][)
end
Sample Output
6
6
4
0
6
思路:
运用DP算法求解,定义dp[i][j] 为区间 i 到 j 中的括号最大匹配数目,若已知区间 i+1 到 j-1 的括号匹配数目,且 i 与 j 的 括号匹配则可推出
dp[i][j]=dp[i+1][j-1]+2;
所以我们只要枚举 i 到 j 的括号数目再通过求区间内的最大括号数目,即可求得 dp[i][j]的最大括号数目
通过枚举 i 和 j 的中间值,即可求得dp[i][j] 的最大值
dp[i][j]=max(dp[i][j],dp[i][m]+dp[m+1][j]) ;
代码实现
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <string>
using namespace std;
const int N=105;
int dp[N][N];
int main()
{
string c;
while(cin>>c)
{
if(c=="end") break;
memset(dp,0,sizeof(dp));
for(int i=1;i<c.size();i++)
{
for(int j=0,k=i;k<c.size();k++,j++)
{
if((c[j]=='(' && c[k]==')') || (c[j]=='['&&c[k]==']'))
dp[j][k]=dp[j+1][k-1]+2;
for(int m=j;m<k;m++)
dp[j][k]=max(dp[j][k],dp[j][m]+dp[m+1][k]);
}
}
printf("%d\n",dp[0][c.size()-1]);
}
return 0;
}
四边形不等式优化
四边形不等式的证明:
四边形不等式优化讲解(详解)
当决策代价函数w[i][j] 满足w[i][j]+w[i’][j’]<=w[I;][j]+w[i]
j’ 时,称w满足四边形不等式.当函数w[i][j] 满足w[i’][j]<=w[i][j’] i<=i’<=j<=j’) 时,称w关于区间包含关系单调.如果状态转移方程m为
且决策代价w满足四边形不等式的单调函数(可以推导出m亦为满足四边形不等式的单调函数),则可利用四边形不等式推出最优决策s的单调函数性,从而减少每个状态的状态数,将算法的时间复杂度由原来的O(n3)降低为O(n2).方法是通过记录子区间的最优决策来减少当前
的决策量
参考代码
memset(s,0,sizeof s);
memset(dp, 0x3f, sizeof dp);
for (int i = 1; i <= n; i++)
{
dp[i][i] = 0;
s[i][i] = i;
}
for (int len = 2; len <= n; len++)
{
for (int i = 1; i <= n; i++)
{
int j = i + len - 1;
if (j > n) continue;
for (int k=s[i][j-1]; k<=s[i+1][j]; k++)
{
int tmp=dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1];
if (tmp < dp[i][j])
{
dp[i][j] = tmp;
s[i][j] = k;
}
}
}
}
return dp[1][n];