题目背景
给定一个正整数序列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个中间和,按照从里到外,从左到右的顺序输出。
输入输出样例
输入 #1复制
4 4 1 2 3
输出 #1复制
(4+((1+2)+3)) 19 3 6 10
说明/提示
范围在题目上有说明。
思路
本题难度:
第2问:普及+/提高
第1、3问:IOI+
首先第2问很像石子合并那题,用区间dp就好,然后一顿区间dp的套路即可:枚举区间长度,枚举区间左端点,求出区间右端点,枚举断点。
令dp[i][j]为区间[i,j]合并后能获得的最小和,则:
dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1)
其中k是区间[i,j]中的一个断点,sum[j]-sum[i-1]为区间[i,j]和。
答案为[1,n]。
如何输出括号呢?
用la与ra数组记录一个数左右的括号数,然后就可以递归了。
#include <stdio.h>
#include <iostream>
#include <memory.h>
#define inf 2e9+7
#define int long long int
using namespace std;
int a[21],n,s,dp[21][21],sum[21],point[21][21];
int la[21],ra[21],ans[21],cnt;
void dfs(int l,int r)
{
if(l==r) return;
la[l]++;
ra[r]++;
dfs(l,point[l][r]);
dfs(point[l][r]+1,r);
ans[++cnt]=sum[r]-sum[l-1];//合并一次能得到的值
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
register int i,j,k,l;
cin>>n;
memset(dp,inf,sizeof(dp));
for(i=1;i<=n;i++)
{
cin>>a[i];
sum[i]=sum[i-1]+a[i];
dp[i][i]=0;
}
for(l=2;l<=n;l++)
{
for(i=1;i+l-1<=n;i++)
{
j=i+l-1;
for(k=i;k<=j;k++)
{
if(dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1]<=dp[i][j])
{
dp[i][j]=dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1];
point[i][j]=k;//区间[l,r]取到最优解点的断点
}
}
}
}
dfs(1,n);
for(i=1;i<=n;i++)
{
for(j=1;j<=la[i];j++)
{
cout<<'(';
}
cout<<a[i];
for(j-1;j<=ra[i];j++)
{
cout<<')';
}
if(i!=n)
{
cout<<'+';
}
}cout<<endl;
cout<<dp[1][n]<<endl;
for(i=1;i<=cnt;i++)
{
cout<<ans[i]<<' ';
}cout<<endl;
return 0;
}