题目背景
给定一个正整数序列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+((1+2)+3))
19
3 6 10
题解:第二问的答案其实就是合并石头,dp(i,j)表示i到j 合并后的最小值。
首先需要用sum[i]预处理sum[j]-sum[i-1]表示区间的合并和;
dp(i,j) = min(dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1])
k表示枚举的断点,i<=k<j;
重点就是第一问和第三问。
对于这两问可以一起求。
用一个 dfs 从[1,n] 这个状态往回推每次找到一个 x 表示 当前 [l,r] 状态是用 [l,x] 和 [x+1,r] 转移而来的。
然后对于第一问,可以这样考虑,记录一个 numL[i] 表示 i 作为 l 被递归了几次。由题目的观察可以发现,i 作为 l 被递归的次数(除 l=r=i的情况时) 就是 i 前面(与 i 相连)的左括号数。
类似的,记录一个numR[i] 表示 i 作为 r 被递归了几次。还是由题目的观察可以发现,i 作为 r 被递归的次数(除 l=r=i的情况时) 就是 i 后面的(与 i 相连)右括号数。
当然 如果numL[i]≠0 则 numR[i]=0 因为除去了 l=r=i 的情况时。
然后在输出的时候 对于每一个数前面输出 numL[i] 个‘(’ 后面输出numR[i] 个‘)’ 然后在两个数之间加 ‘+’字符即可。
对于第二问,由于是dfs将[1,n]状态进行分割,所以就是在回溯时,直接记录 sum[l,r] 就好辣。
然后就愉快的AC了
#include<stdio.h> #include<bits/stdc++.h> using namespace std; int kr[50],pa[50][50],a[50],kl[50],g[50][50],dp[50][50],sum[61]; void dfs(int x , int y) { if(x==y) return ; kl[x]++; kr[y]++; dfs(x,g[x][y]); dfs(g[x][y]+1,y); } void DFS(int x , int y) { if(x==y) return ; DFS(x,g[x][y]); DFS(g[x][y]+1,y); cout<<sum[y]-sum[x-1]<<' '; } int main() { int n; scanf("%d",&n); for(int i=1 ; i<=n ; i++) { scanf("%d",&a[i]); sum[i]=sum[i-1]+a[i]; dp[i][i]=0; } for(int i=n-1 ; i ; i--) { for(int j=i+1 ; j<=n ; j++) { dp[i][j]=1e9; for(int k=i ; k<j ; k++) { if(dp[i][j]>=dp[i][k] + dp[k+1][j] + sum[j] - sum[i-1]) { dp[i][j]=dp[i][k] + dp[k+1][j] + sum[j] - sum[i-1]; g[i][j]=k; } } } } dfs(1,n); for(int i=1 ; i<=n ; i++) { for(int j=1 ; j<=kl[i] ; j++) cout<<'('; cout<<a[i]; if(!kr[i]&&i!=n) cout<<'+'; for(int j=1 ; j<=kr[i] ; j++) cout<<')'; if(kr[i]&&i!=n) cout<<'+'; } cout<<endl; cout<<dp[1][n]<<endl; DFS(1,n); }