C++平行四边形不等式优化解题报告:[DP]添加括号----平行四边形不等式优化DP

本文深入探讨了动态规划(DP)算法在解决特定数学问题中的应用,特别是通过添加括号以寻找序列中最小中间和之和的最优策略。文章详细介绍了DP的基本思路,包括状态转移方程的推导,以及如何利用平行四边形不等式优化算法的时间复杂度。通过对比优化前后代码,展示了优化带来的显著效果。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

添加括号

题目描述

输入

输出

样例输入

样例输出

方法

DP思路

朴素代码

方法改进

平行四边形不等式优化

改进代码

尾语


添加括号

时间限制: 1 Sec  内存限制: 64 MB

题目描述

给定一个正整数序列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个中间和,求出使中间和之和最小的添括号方法。

输入

共两行。 第一行,为整数n。(1<=n<=20) 第二行,为a(1),a(2),...,a(n)这n个正整数,每个数字不超过100。

输出

输出3行。 第一行,为添加括号的方法。 第二行,为最终的中间和之和。 第三行,为n-1个中间和,按照从里到外,从左到右的顺序输出。

样例输入

4
4 1 2 3

样例输出

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

方法

DP思路

这道题是出自DP的一道题。既然是出自DP,就用DP做吧。

观察样例,我们发现:由于必须有n-1个中间和,所以数列只能两两相加,如(1,2,3),只能是((1+2)+3),不能是(1+2+3)。所以,要把任意一个区间内的所有数字加起来,必定会把中间两个区间相加,且只能由这两个区间相加。换句话说,每一个区间都是由两个区间相加得到的

  f[i][j]表示把区间(i~j)全部加起来的最小代价,由此我们可以得到状态转移方程:

f[i][j]=min(f[i][k]+f[k+1][j]+sum(i~j)) (i≤k≤j)。

sum(i~j)表示区间(i~j)所有数字的总和,因为把它们所有加起来所得的和就是它们所有数字的总和。k是其中两个区间的交界处(专业术语叫它“决策点”),只要枚举这个决策点,就可以得到答案。

但是在成默的千年灵芝的提醒下,我发现此处有一个坑:如(1,2,4,1,2)的答案是“(((1+2)+4)+(1+2))”而不是“((1+2)+(4+(1+2)))”,它的k值在后面,而不是第一个“最优”k,所以判断当前f[i][j]与f[i][k]+f[k+1][j]+sum(i~j)的大小时,应该是(f[i][k]+f[k+1][j]+sum(i~j) <= f[i][j])。

sum(i~j)可以用前缀和相减来得出。

输出括号表达式时可以通过把f[i][j]对应的k值存下来,就可以用递归二分输出它。

普通代码如下:

朴素代码

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
int n,i,j,k,a[25],f[25][25],s[25][25];
inline int read(){  //读入优化
    int x=0,f=1;char s=getchar();
    while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
    while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
    return x*f;
}
void write(int x){  //输出优化
    if(x/10>0)write(x/10);
    putchar(char(x%10+'0'));
}
void print(int l,int r){
    if(l>r)return;
    if(l==r)write(a[r]-a[l-1]);
    else{
    putchar('(');
    print(l,s[l][r]);
    if(s[l][r]<r)putchar('+');
    print(s[l][r]+1,r);
    putchar(')');
    }
}
void prints(int l,int r){
    if(l>=r)return;
    prints(l,s[l][r]);
    prints(s[l][r]+1,r);
    write(a[r]-a[l-1]);
    if(l!=1||r!=n)putchar(' ');
}
int main()
{
    n=read();
    memset(f,0x3f,sizeof(f));
    for(i=1;i<=n;i++)a[i]=read(),a[i]+=a[i-1],f[i][i]=0,s[i][i]=i;
    if(n==1){
        putchar('(');write(a[1]);putchar(')');
        putchar('\n');
        putchar('0');putchar('\n');
        return 0;
    }
    for(i=n;i>0;i--)
        for(j=i+1;j<=n;j++)
            for(k=i;k<=j;k++)
                if(f[i][k]+f[k+1][j]+a[j]-a[i-1]<=f[i][j]){
                    f[i][j]=f[i][k]+f[k+1][j]+a[j]-a[i-1];
                    s[i][j]=k;
                }
    print(1,n);
    putchar('\n');
    write(f[1][n]);
    putchar('\n');
    prints(1,n);
    putchar('\n');
    return 0;
}

方法改进

平行四边形不等式优化

细心发现:这道题是可以用平行四边形不等式优化的!

这道题其实很像合并石子,如果数据为(1<= n <=5000),那就得用平行四边形不等式优化了。

真巧,平行四边形不等式优化我也是才学,想了解的请看博客:平行四边形不等式优化详解

优化后平摊下来就为O(n^{2}),就不会超时了。

上面代码把每个f[i][j]对应的k值都存下来了,所以顺便就优化了。

下面是优化后的代码:

改进代码

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
int n,i,j,k,a[25],f[25][25],s[25][25];
inline int read(){  //读入优化
    int x=0,f=1;char s=getchar();
    while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
    while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
    return x*f;
}
void write(int x){  //输出优化
    if(x/10>0)write(x/10);
    putchar(char(x%10+'0'));
}
void print(int l,int r){
    if(l>r)return;
    if(l==r)write(a[r]-a[l-1]);
    else{
    putchar('(');
    print(l,s[l][r]);
    if(s[l][r]<r)putchar('+');
    print(s[l][r]+1,r);
    putchar(')');
    }
}
void prints(int l,int r){
    if(l>=r)return;
    prints(l,s[l][r]);
    prints(s[l][r]+1,r);
    write(a[r]-a[l-1]);
    if(l!=1||r!=n)putchar(' ');
}
int main()
{
    n=read();
    memset(f,0x3f,sizeof(f));
    for(i=1;i<=n;i++)a[i]=read(),a[i]+=a[i-1],f[i][i]=0,s[i][i]=i;
    if(n==1){
        putchar('(');write(a[1]);putchar(')');
        putchar('\n');
        putchar('0');putchar('\n');
        return 0;
    }
    for(i=n;i>0;i--)
        for(j=i+1;j<=n;j++)
            for(k=s[i][j-1];k<=s[i+1][j];k++)
                if(f[i][k]+f[k+1][j]+a[j]-a[i-1]<=f[i][j]){
                    f[i][j]=f[i][k]+f[k+1][j]+a[j]-a[i-1];
                    s[i][j]=k;
                }
    print(1,n);
    putchar('\n');
    write(f[1][n]);
    putchar('\n');
    prints(1,n);
    putchar('\n');
    return 0;
}

也没有太大差别。

尾语

欢迎关注我的博客 偶耶(xiong j x)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值