題目背景
給定一個正整數序列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;
}