首页 > 技术文章 > 区间DP小结

Y-sofun 2017-10-11 20:53 原文

临近NOIP,蒟蒻决定对自己所学过的知识进行一波总结。

 

区间DP是一种非常常见、非常实用的算法。

一般是用于维护区间$[1,n]$中的最值的算法,通过枚举不同的区间长度,来达到向后寻找的目的。

一般的状态转移方程如下:

$dp[i][j]=max(dp[i][j],dp[i][k]+dp[k+1][j]+......)$

 

例题:

题目描述

在一个圆形操场的四周摆放N堆石子,现要将石子有次序地合并成一堆.规定每次只能选相邻的2堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分。

试设计出1个算法,计算出将N堆石子合并成1堆的最小得分和最大得分.

输入输出格式

输入格式:

 

数据的第1行试正整数N,1≤N≤100,表示有N堆石子.第2行有N个数,分别表示每堆石子的个数.

 

输出格式:

 

输出共2行,第1行为最小得分,第2行为最大得分.

 

输入输出样例

输入样例#1:
4
4 5 9 4
输出样例#1:
43
54

基础模板,通过维护从小到大的不同区间长度的最值,来达到求出答案的目的。
用一个前缀和维护就可以在O(1)的时间里找到合并的值。(后面的区间DP,博主都是用的枚举区间长度作为阶段)
#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;

const int MAXN=210;

int n;
int a[MAXN];
int dp[MAXN][MAXN];
int f[MAXN][MAXN];

int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        a[i+n]=a[i];
    }
    for(int i=2;i<=2*n;i++) a[i]+=a[i-1];
    for(int i=2*n-1;i;i--)
        for(int j=i+1;j<=2*n;j++)
        {
            int b=0x7f7f7f7f;
            for(int k=i;k<j;k++)
            {
                dp[i][j]=max(dp[i][k]+dp[k+1][j]+a[j]-a[i-1],dp[i][j]);
                b=min(f[i][k]+f[k+1][j]+a[j]-a[i-1],b);
            }
            f[i][j]=b;
        }
    int ans1=0,ans2=0x7f7f7f7f;
    for(int i=1;i<=n;i++)
    {
        ans1=max(ans1,dp[i][i+n-1]);
        ans2=min(ans2,f[i][i+n-1]);
    }
    printf("%d\n%d",ans2,ans1);
}
View Code

 

题目描述

在Mars星球上,每个Mars人都随身佩带着一串能量项链。在项链上有N颗能量珠。能量珠是一颗有头标记与尾标记的珠子,这些标记对应着某个正整数。并且,对于相邻的两颗珠子,前一颗珠子的尾标记一定等于后一颗珠子的头标记。因为只有这样,通过吸盘(吸盘是Mars人吸收能量的一种器官)的作用,这两颗珠子才能聚合成一颗珠子,同时释放出可以被吸盘吸收的能量。如果前一颗能量珠的头标记为m,尾标记为r,后一颗能量珠的头标记为r,尾标记为n,则聚合后释放的能量为m*r*n(Mars单位),新产生的珠子的头标记为m,尾标记为n。

需要时,Mars人就用吸盘夹住相邻的两颗珠子,通过聚合得到能量,直到项链上只剩下一颗珠子为止。显然,不同的聚合顺序得到的总能量是不同的,请你设计一个聚合顺序,使一串项链释放出的总能量最大。

例如:设N=4,4颗珠子的头标记与尾标记依次为(2,3) (3,5) (5,10) (10,2)。我们用记号⊕表示两颗珠子的聚合操作,(j⊕k)表示第j,k两颗珠子聚合后所释放的能量。则第4、1两颗珠子聚合后释放的能量为:

(4⊕1)=10*2*3=60。

这一串项链可以得到最优值的一个聚合顺序所释放的总能量为

((4⊕1)⊕2)⊕3)=10*2*3+10*3*5+10*5*10=710。

输入输出格式

输入格式:

 

输入的第一行是一个正整数N(4≤N≤100),表示项链上珠子的个数。第二行是N个用空格隔开的正整数,所有的数均不超过1000。第i个数为第i颗珠子的头标记(1≤i≤N),当i<N< span>时,第i颗珠子的尾标记应该等于第i+1颗珠子的头标记。第N颗珠子的尾标记应该等于第1颗珠子的头标记。

至于珠子的顺序,你可以这样确定:将项链放到桌面上,不要出现交叉,随意指定第一颗珠子,然后按顺时针方向确定其他珠子的顺序。

 

输出格式:

 

输出只有一行,是一个正整数E(E≤2.1*10^9),为一个最优聚合顺序所释放的总能量。

 

输入输出样例

输入样例#1:
4
2 3 5 10
输出样例#1:
710

说明

NOIP 2006 提高组 第一题

 

同样是一道模板题。具体看注释。

 

#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;

const int MAXN=110;

int dp[MAXN*2][MAXN*2];
int ener[MAXN*2];

int n;

int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&ener[i]);
        ener[i+n]=ener[i];
    }
    
    for(int i=2;i<=n;i++)
        for(int l=1,r;l<=2*n-i+1;l++)
        {
            r=l+i-1;
            for(int k=l;k<r;k++)
                dp[l][r]=max(dp[l][r],dp[l][k]+dp[k+1][r]+ener[l]*ener[k+1]*ener[r+1]);//dp[l][k]表示l~k已经被合并为一颗珠子了,所以用l作为开始,到下一颗珠子k+1,k+1~r又被合并了,所以下一颗珠子为r+1
        }
    
    int ans=0;
    for(int i=1;i<=n;i++)
    {
        ans=max(ans,dp[i][i+n-1]);
    }
            
    printf("%d",ans);
}
View Code

 

 

Light OJ-1422

Gappu has a very busy weekend ahead of him. Because, next weekend is Halloween, and he is planning to attend as many parties as he can. Since it's Halloween, these parties are all costume parties, Gappu always selects his costumes in such a way that it blends with his friends, that is, when he is attending the party, arranged by his comic-book-fan friends, he will go with the costume of Superman, but when the party is arranged contest-buddies, he would go with the costume of 'Chinese Postman'.

Since he is going to attend a number of parties on the Halloween night, and wear costumes accordingly, he will be changing his costumes a number of times. So, to make things a little easier, he may put on costumes one over another (that is he may wear the uniform for the postman, over the superman costume). Before each party he can take off some of the costumes, or wear a new one. That is, if he is wearing the Postman uniform over the Superman costume, and wants to go to a party in Superman costume, he can take off the Postman uniform, or he can wear a new Superman uniform. But, keep in mind that, Gappu doesn't like to wear dresses without cleaning them first, so, after taking off the Postman uniform, he cannot use that again in the Halloween night, if he needs the Postman costume again, he will have to use a new one. He can take off any number of costumes, and if he takes off k of the costumes, that will be the last k ones (e.g. if he wears costume A before costume B, to take off A, first he has to remove B).

Given the parties and the costumes, find the minimum number of costumes Gappu will need in the Halloween night.

Input

Input starts with an integer T (≤ 200), denoting the number of test cases.

Each case starts with a line containing an integer N (1 ≤ N ≤ 100) denoting the number of parties. Next line contains N integers, where the ith integer ci (1 ≤ ci ≤ 100) denotes the costume he will be wearing in party i. He will attend party 1 first, then party 2, and so on.

output

For each case, print the case number and the minimum number of required costumes.

Sample Input

2

4

1 2 1 2

7

1 2 1 1 3 2 1

Output of Sample Input

Case 1: 3

Case 2: 4

 

题目大意:

给出n个派对需要穿的服装,穿服装时可以一件套在一件外面,脱了的服装不能再穿,如果需要则须再穿一件新的服装。

思路就是区间DP,虽然看着不太像是裸题,但仔细分析就不难发现,这道题只需要将每个区间所需的最小的服装数求出,再对更大的区间进行处理即可。

根据思路并不难得到$dp[i][j]=dp[i][j-1]+1$当然$dp[i][j]=dp[i+1][j]+1$也是可行的。

再枚举每一个子区间进行求解。

$for(k=i;k<j;k++)$

$if(a[j]==a[k]) dp[i][j]=min(dp[i][k]+dp[k+1][r-1],dp[i][j]);$

#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;

const int MAXN=110;

int t,n;
int a[MAXN];
int dp[MAXN][MAXN];

void DP()
{
    memset(dp,0,sizeof dp);
    for(int i=1;i<=n;i++) dp[i][i]=1;
    for(int i=2;i<=n;i++)
    {
        for(int l=1,r;l<=n-i+1;l++)
        {
            r=l+i-1;
            dp[l][r]=dp[l][r-1]+1;
            for(int mid=l;mid<r;mid++)
                if(a[r]==a[mid]) dp[l][r]=min(dp[l][mid]+dp[mid+1][r-1],dp[l][r]);
        }
    }
}

int main()
{
    scanf("%d",&t);
    for(int i=1;i<=t;i++)
    {
        scanf("%d",&n);
        for(int j=1;j<=n;j++) scanf("%d",&a[j]);
        DP();
        printf("Case %d: %d\n",i,dp[1][n]);
    }
}
View Code

 

推荐阅读