目录
添加括号
时间限制: 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),那就得用平行四边形不等式优化了。
真巧,平行四边形不等式优化我也是才学,想了解的请看博客:平行四边形不等式优化详解
优化后平摊下来就为,就不会超时了。
上面代码把每个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)