题面
题目描述
给定一个正整数序列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个中间和,按照从里到外,从左到右的顺序输出。
题解
这道题目和合并石子真的很像!每次合并的代价变成某一个区间的累加和。
所以只要处理一个前缀和数组就好了。
我们处理一个新数组
add[i][j]=k
a
d
d
[
i
]
[
j
]
=
k
,表示
f[i][j]
f
[
i
]
[
j
]
是由
f[i][k]
f
[
i
]
[
k
]
和
f[k+1][j]
f
[
k
+
1
]
[
j
]
这两个状态合并而来。
那么输出方案递归求解。具体方法见代码
code
#include<bits/stdc++.h>
using namespace std;
inline int read()
{
int num=0;
char c=' ';
bool flag=true;
for(;c>'9'||c<'0';c=getchar())
if(c=='-')
flag=false;
for(;c>='0'&&c<='9';num=num*10+c-48,c=getchar());
return flag ? num : -num;
}
const int maxn=25;
int f[maxn][maxn],add[maxn][maxn];
int a[maxn],A[maxn];
int n;
void init()
{
n=read();
for(int i=1;i<=n;++i)
{
a[i]=read();
A[i]=A[i-1]+a[i];
}
memset(f,10,sizeof f);
for(int i=0;i<=n;i++)
f[i][i]=0;
}
void DP()
{
for(int len=2;len<=n;len++)
{
for(int i=1;i+len-1<=n;i++)
{
int j=i+len-1;
for(int 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];//
//状态转移方程
add[i][j]=k;
//合并的中间点
}
}
}
}//裸区间DP
void work1(int l,int r)//递归输出方案
{
if(l==r)
{
printf("%d",a[l]);
return ;
}//如果区间被分解只剩下一个元素,那么直接输出,
printf("(");
work1(l,add[l][r]);//中间点左边是加号左边
printf("+");
work1(add[l][r]+1,r);//中间点右边是加号右边
printf(")");
}
void work2(int l,int r)//输出区间和之和
{
if(!add[l][r])
return ;
work2(l,add[l][r]);
work2(add[l][r]+1,r);
if(l==1&&r==n)
printf("%d",A[r]-A[l-1]);
//输出格式,不忽略行末空格。
//所以这里要特判去掉空格。
//坑爹的HLOJ要判WA的……
else
printf("%d ",A[r]-A[l-1]);
}
void print()
{
work1(1,n);
printf("\n%d\n",f[1][n]);
work2(1,n);
}
int main()
{
init();
DP();
print();
return 0;
}