添加括号 vijos1038 动态规划 区间DP

背景

给定一个正整数序列a(1),a(2),...,a(n),(1<=n<=20)
不改变序列中每个元素在序列中的位置,把它们相加,并用括号记每次加法所得的和,称为中间和。

例如:
给出序列是4,1,2,3。

第一种添括号方法:
((4+1)+(2+3))=((5)+(5))=(10)
有三个中间和是5,5,10,它们之和为:5+5+10=20
第二种添括号方法

(4+((1+2)+3))=(4+((3)+3))=(4+(6))=(10)
中间和是3,6,10,它们之和为19。

描述

现在要添上n-1对括号,加法运算依括号顺序进行,得到n-1个中间和,求出使中间和之和最小的添括号方法。如果有多组最优解,考虑将括号尽可能往前放。

样例输入

4
4 1 2 3

样例输出

(4+((1+2)+3))
19
3 6 10

这道题和合并石子那一道DP很像,不同的是这一次要打印路径。

因为要记录路径,所以选择使用记忆化搜索,比DP的代码更清晰易读。

用root[l,r]表示这个区间的断点位置,dp[i][j]表示[i,j]区间的最优答案

显然有:

0、对于一段区间合并产生的价值,就是这个区间的值的和。因此我们可以采用前缀和进行优化

1、对于任意 i∈[1,n] 有 dp[i][i] = 0 (不用合并)

2、对于任意 i∈[1,n) 有 dp[i][i+1]=a[i]+a[i+1] (只有两个,一定合并)

3、对于任意一组 (l,r),r>l且r-l>1,那么就有dp[l,r]=min(dp[l][i]+dp[i+1][r]+sum[r]-sum[l-1];)其中i∈[l,r)  (枚举断点 区间DP)

取到最优值的时候,记录断点即可。后续递归输出。

#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
template<class T> inline void read(T &_a){
    bool f=0;int _ch=getchar();_a=0;
    while(_ch<'0' || _ch>'9'){if(_ch=='-')f=1;_ch=getchar();}
    while(_ch>='0' && _ch<='9'){_a=(_a<<1)+(_a<<3)+_ch-'0';_ch=getchar();}
    if(f)_a=-_a;
}

int n,a[21],dp[21][21],root[21][21],sum[21],zuo[21],you[21];
bool vis[21][21];

void dfs(int l,int r)
{
    vis[l][r]=true;
    if(l==r) {dp[l][r]=0; return ;}
    for (register int i=1;i<=n;++i)
        for (register int v=l;v+i-1<=r;++v)
            if(!vis[v][v+i-1]) dfs(v,v+i-1);
    if(r==l+1)
    {
        root[l][r]=l;
        dp[l][r]=a[l]+a[r];
        return ;
    }
    for (register int i=l;i<r;++i)
    {
        if(dp[l][i]+dp[i+1][r]+sum[r]-sum[l-1]<=dp[l][r])
        {
            dp[l][r]=dp[l][i]+dp[i+1][r]+sum[r]-sum[l-1];
            root[l][r]=i;
        }
    }
}

void print(int l,int r)
{
    if(l==r) printf("%d",a[l]);
    if(l>=r) return ;
    printf("(");
    print(l,root[l][r]);
    printf("+");
    print(root[l][r]+1,r);
    printf(")");
}

void prints(int l,int r)
{
    if(!root[l][r]) return ;
    prints(l,root[l][r]);
    prints(root[l][r]+1,r);
    printf("%d ",sum[r]-sum[l-1]);
}

int main()
{
    read(n);
    for (register int i=1;i<=n;++i) read(a[i]),sum[i]=sum[i-1]+a[i];
    memset(dp,0x7f,sizeof(dp));
    dfs(1,n);
    print(1,n);
    printf("\n%d\n",dp[1][n]);
    prints(1,n);
    return 0;
}

 

posted @ 2017-11-02 21:43  JayWang  阅读(264)  评论(0编辑  收藏  举报