[洛谷]P2308 添加括號 (#區間dp)

題目背景

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

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章