区间dp:

区间dp就是在区间上进行动态规划,求解一段区间上的最优解。主要是通过合并小区间的 最优解进而得出整个大区间上最优解的dp算法。

主要思路:既然让我求解在一个区间上的最优解,那么我把这个区间分割成一个个小区间,求解每个小区间的最优解,再合并小区间得到大区间即可。

例题1:括号匹配

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

用二维数组f[i][j]表示从i~j的最大匹配数,还有个比较重要的思想是拆分思想,就是把f[i][j],拆分为f[i][k]+f[k+1][j]两部分的和,在取最大,就是dp的选择过程。
AC代码:

#include<iostream>
#include<cstring>
using namespace std;
int f[110][110];
int main()
{
    string a;
    while(cin>>a&&a!="end")
    {
        memset(f,0,sizeof(0));//初始化
        int n=a.length();
        for(int d=2;d<=n;d++)//间隔长度,括号总是成对出现,所以间隔取2.
        {
            for(int i=0;i<n;i++)
            {
                int j=i+d-1;//每一段的右结点
                if(j>n) break;
                if((a[i]=='('&&a[j]==')')||(a[i]=='['&&a[j]==']'))//匹配
                f[i][j]=f[i+1][j-1]+2;//+2是因为括号是一对
                else
                f[i][j]=f[i+1][j-1];//不匹配则不变
                for(int k=i;k<j;k++)
                {
                    f[i][j]=max(f[i][j],f[i][k]+f[k+1][j]);//核心,拆分过程
                }
            }
        }
        cout<<f[0][n-1]<<"\n";
    }
}

例题2:(经典)石子合并问题--直线版

一条直线上摆放着一行共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的经典问题,很容易联想到贪心算法里面的合并果子,但那个是任意两堆可以合并,但是这个题中只能是相邻的合并,嗯,That's good~。
和上个题一样,我们需要设置maxx[i][j]和minn[i][j]分别来储存从i~j堆的合并最小消耗值。并用sum[i]记录一下前缀和。
复杂度O(n3)
AC代码:

#include<bits/stdc++.h>
using namespace std;
int maxx[110][110];
int minn[110][110];
int sum[110];
int main()
{
    int n;
    while(cin>>n)
    {
        sum[0]=0;
        memset(maxx,0,sizeof(maxx));
        memset(minn,0,sizeof(minn));
        for(int i=1;i<=n;i++)
        {
            cin>>sum[i];
            sum[i]+=sum[i-1];
        }
        for(int d=1;d<=n;d++)
        {
            for(int i=1;i+d<=n;i++)
            {
                int j=i+d;
                minn[i][j]=99999999;//除了min[i][i]都赋值最大,因为自己合并自己消耗值是0.
                for(int k=i;k<j;k++)
                {
                    maxx[i][j]=max(maxx[i][j],maxx[i][k]+maxx[k+1][j]+sum[j]-sum[i-1]);
                    minn[i][j]=min(minn[i][j],minn[i][k]+minn[k+1][j]+sum[j]-sum[i-1]);
                }
            }
        }
        cout<<minn[1][n]<<' '<<maxx[1][n]<<'\n';

    }
}

但是复杂度非常高,如果n<=1000就无法通过了,所以我们需要考虑用四边形不等式优化,接近O(n2).
(暂时还不会,会了再贴上)

例题3:(升级)石子合并问题--圆形版

在圆形操场上摆放着一行共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过程相同,唯一不同的就是dp结束后,需要寻找开始和结束点,来使消耗值最小。
AC代码:

#include<bits/stdc++.h>
using namespace std;
int maxx[220][220];
int minn[220][220];
int sum[220];
int a[110];
int main()
{
    int n;
    while(cin>>n)
    {
        sum[0]=0;
        memset(maxx,0,sizeof(maxx));
        memset(minn,0,sizeof(minn));
        for(int i=1;i<=n;i++)
        {
            cin>>a[i];
            sum[i]=sum[i-1]+a[i];
        }
        for(int i=n+1;i<=2*n;i++)//2*n!
        sum[i]=sum[i-1]+a[i-n];
        for(int d=1;d<=n*2;d++)
        {
            for(int i=1;i+d<=n*2;i++)
            {
                int j=i+d;
                minn[i][j]=99999999;//除了min[i][i]都赋值最大,因为自己合并自己消耗值是0.
                for(int k=i;k<j;k++)
                {
                    maxx[i][j]=max(maxx[i][j],maxx[i][k]+maxx[k+1][j]+sum[j]-sum[i-1]);
                    minn[i][j]=min(minn[i][j],minn[i][k]+minn[k+1][j]+sum[j]-sum[i-1]);
                }
            }
        }
        int ansmin=99999999;
        int ansmax=0;
        for(int i=1;i<=n;i++)
        {
            ansmin=min(ansmin,minn[i][i+n-1]);
            ansmax=max(ansmax,maxx[i][i+n-1]);
        }
        cout<<ansmin<<' '<<ansmax<<'\n';

    }
}

文章作者: Hao.Jia
版权声明: 本站所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Hao.Jia's Blog
算法 算法碎片,经典,板子
喜欢就支持一下吧