我们先看一道经典题目:给出一个字符串,仅含()[]四个字符,问整个字符串里最多有多少个匹配的括号。
Sample Input
((()))
()()()
([]])
)[)(
([][][)
end
Sample Output
6
6
4
0
6
区间dp的要点:
1. dp[i][j]表示从i到j最多匹配的括号数。
2. 外重循环枚举区间长度,内重循环枚举左端点。
状态转移方程:
if(judge(ll,rr))//i和j处的括号匹配
dp[ll][rr]=dp[ll+1][rr-1]+2;
3. 枚举ll到rr的所有中间点k:dp[ll][rr]=max(dp[ll][rr],dp[ll][k]+dp[k+1][rr]);
这一步是必须的,如果没有这一步,对于以下字符串:
0 1 2 3
( ) ( )
因为0和3匹配,我们就使得dp[0][3]=dp[1][2]+2=2了,然而实际上0和1也是匹配的,2和3也是匹配的。
这种问题会出现的原因是:
if(judge(ll,rr))
dp[ll][rr]=dp[ll+1][rr-1]+2;
这句状态转移方程,只考虑了区间内紧挨着ll和rr是否匹配,而ll到rr之间,如果也出现了与端点ll或rr的括号匹配的情况,则被忽略了。
所以有了以下这部分代码用于判断整个ll到rr区间上与端点匹配的括号,对dp[i][j]的影响:
for(int k=ll; k<=rr; k++)
dp[ll][rr]=max(dp[ll][rr],dp[ll][k]+dp[k+1][rr]);
My AC code:
#include
#include
#include
const int MAX = 100 + 10;
using namespace std;
int dp[MAX][MAX];
string arr;
//状态转移方程
//if(left与right的括号匹配)
//dp[left][right]=dp[left+1][right-1]+2
//同时:
//对于所有k从“left+1到right”
//dp[left][right]=max(dp[left+1][k]+dp[k+1][right-1])+2
bool judge(int ll,int rr)
{
if(arr[ll]=='('&&arr[rr]==')'||arr[ll]=='['&&arr[rr]==']')
return true;
else
return false;
}
int main()
{
// freopen("in.txt","r",stdin);
while(cin>>arr)
{
memset(dp,0,sizeof dp);
if(arr[0]=='e')
break;
int len=arr.size();
//枚举区间长度cnt
for(int cnt=1; cnt
给出n堆石子,现在我们只能把相邻的石子堆合并,每次合并的花费为2个石子堆的石子个数总和,求合并全部石子堆的最小花费。
样例输入
3
1 2 3
7
13 7 8 16 21 4 18
样例输出
9
239
状态转移方程:
dp[ll][rr]=min(dp[ll][rr],dp[ll][mm]+dp[mm+1][rr]+sum[rr]-sum[ll-1]);
其中sum[i]为数组前i项和。
枚举K相当于枚举两个大堆(经过重重合并)的分割点,那么状态转移方程也就不言而喻了。dp[ll][mm]相当于左面那个堆合并的最小代价,dp[mm+1][rr]是右面堆合并的最小代价。
要合并这两个大堆又需要sum[ll][rr]的代价,故得以上方程。
步骤依旧是:
1. 枚举区间长度
2. 枚举左端点
3. 根据状态转移方程更新区间dp值
My AC code:
#include
#include
#include
const int INF=99999999;
const int MAXN = 200 + 10;
int arr[MAXN];
int dp[MAXN][MAXN];
int sum[MAXN];
using namespace std;
int main()
{
// freopen("in.txt","r",stdin);
int n;
while(cin>>n)
{
memset(dp,0,sizeof dp);
memset(sum,0,sizeof sum);
for(int i=1;i<=n;i++) cin>>arr[i],sum[i]=sum[i-1]+arr[i],dp[i][i]=0;
for(int cnt=1;cnt<=n;cnt++)
{
for(int ll=1;ll+cnt<=n;ll++)
{
int rr=ll+cnt;
dp[ll][rr]=INF;
for(int mm=ll;mm
Given n balloons, indexed from 0 to n-1. Each balloon is painted with a number on it represented by array nums. You are asked to burst all the balloons. If the you burst balloon i you will get nums[left] * nums[i] * nums[right] coins. Here left and right are adjacent indices of i. After the burst, the left and right then becomes adjacent.
Find the maximum coins you can collect by bursting the balloons wisely.
Note:
(1) You may imagine nums[-1] = nums[n] = 1. They are not real therefore you can not burst them.
(2) 0 ≤ n ≤ 500, 0 ≤ nums[i] ≤ 100
Example:
Given [3, 1, 5, 8]
Return 167
nums = [3,1,5,8] --> [3,5,8] --> [3,8] --> [8] --> []
coins = 3*1*5 + 3*5*8 + 1*3*8 + 1*8*1 = 167
给你一堆气球,每个气球有一个num,每次扎破一个气球,付出的代价是这个气球及其左右相邻的气球num乘积。现在我们要把这些气球全部扎破,求最大的代价。
和石子归并不同的是,石子归并中我们枚举ll到rr之间的所有数,是为了求把ll到rr分割成两个区间的点。对于每个这样的点,我们归并时都要求它前后两个区间的dp和和整个区间的和。
石子归并的转移方程:
dp[ll][rr]=min(dp[ll][rr],dp[ll][mm]+dp[mm+1][rr]+sum[rr]-sum[ll-1]);
然而此题中我们枚举这个区间最后一个扎破的气球。对于每个这样的点,我们扎破它时,除了扎破代价(ll处,rr处和自己的乘积),当然还要计算前后两个区间和。
转移方程:
int add=nums[ll-1]*nums[mm]*nums[rr+1];
dp[ll][rr]=max(dp[ll][rr],dp[ll][mm-1]+dp[mm+1][rr]+add);
这里预处理一下,令nums里多加两个数,第一个数和最后一个数是1,便于处理。
My AC code:
class Solution
{
private:
int dp[600][600];
int len;
public:
void init(vector& nums)
{
memset(dp,0,sizeof dp);
len=nums.size();
nums.push_back(1),nums.push_back(1);
for(int i=len; i>=1; i--)
nums[i]=nums[i-1];
nums[0]=1;
}
void solve(vector& nums)
{
for(int cnt=0; cnt<=len; cnt++)
{
for(int ll=1; ll+cnt<=len; ll++)
{
int rr=ll+cnt;
for(int mm=ll; mm<=rr; mm++)
{
int add=nums[ll-1]*nums[mm]*nums[rr+1];
dp[ll][rr]=max(dp[ll][rr],dp[ll][mm-1]+dp[mm+1][rr]+add);
}
}
}
}
int maxCoins(vector& nums)
{
init(nums);
solve(nums);
return dp[1][len];
}
};