1. 程式人生 > >【模板題】動態規劃 石子合併、括號匹配、加分二叉樹——區間dp問題及其整理

【模板題】動態規劃 石子合併、括號匹配、加分二叉樹——區間dp問題及其整理

題目大意:輸入一棵樹的中序遍歷,定義一棵子樹的得分為其左子樹的加分×右子樹的加分+根的分數。求最大得分及先序遍歷

注意:
1、初始化r[i][i]=i,便於輸出
2、初始化dp[i][i-1]=dp[i+1][i]=1。因為在區間中選取一點為root時會取到端點,即左(右)子樹為空的情況,此時得分=左子樹得分*1+根的分數,即給端點情況(實際這樣的子樹不存在)賦值為1
3、遍歷順序:區間長度->區間起點->區間內選取根
4、區間起點的遍歷n-i+1為結束,不要忘了+1

5、區間內選取根兩端點要取到

思路參考             程式碼參考

#include <iostream>
#include<algorithm>
#include<string.h>
using namespace std;
int n,a[31];
long long dp[27][27],r[27][27];//dp[i][j]表示中序遍歷序列從i到j的最高得分
void print(int i,int j)
{
	if (i>j)
		return ;
	cout<<r[i][j]<<" ";
	print(i,r[i][j]-1);
	print(r[i][j]+1,j);
}
int main()
{
   int i,j,k;
   cin>>n;
   memset(dp,0,sizeof(0));
   for (i=1;i<=n;i++)
   {
	   cin>>a[i];
	   dp[i][i]=a[i];
	   dp[i][i-1]=1;dp[i+1][i]=1;//防止左子樹(或右子樹)為空的情況(雖然這個意義上的dp不存在)
	   r[i][i]=i;//初始為葉子節點(每個都為根)
   }
   for (i=2;i<=n;i++)//區間長度
	   for (j=1;j<=n-i+1;j++)//區間起點,注意截止是n-i+1
		   for (k=j;k<=j+i-1;k++)//取區間內一點為root,注意必須兩頭都要取到!
			   if (dp[j][j+i-1]<dp[j][k-1]*dp[k+1][j+i-1]+dp[k][k])
			   {
				   dp[j][j+i-1]=dp[j][k-1]*dp[k+1][j+i-1]+dp[k][k];
				   r[j][j+i-1]=k;
			   }
	cout<<dp[1][n]<<endl;
	print(1,n);
	cout<<endl;
    return 0;
}

題目連結:

題目大意:n堆石子圍成圈,每次合併得分為合併後石子的重量,求合併成一堆石子後的最大得分和最小得分

思路:    思路參考
1、將前n-1堆石子挪到第n堆後面,則考慮合併2n-1堆石子

2、dp[i][j]是將第i到第j堆合併為一堆的總得分

注意:
1、dp[i][i]初始為0
2、遍歷的區間,len為[2,n],i為[1,2n-1],k為[i,j-1],k必須小於j,不允許出現dp[i+1][i]的不合法情況

3、最終結果還需一重迴圈,是dp[i][i+n-1]的最大值

#include <iostream>
#include<algorithm>
#include<string.h>
using namespace std;
int n,a[101];
int dp[201][201],fp[201][201];//dp[i][j]將第i到第j堆合併為一堆的總得分
int sum[201];//儲存第1到第i堆石子的和
int main()
{
   int len,j,k,i;
   cin>>n;
   memset(dp,0,sizeof(dp));
   memset(fp,0x3f,sizeof(fp));
   memset(sum,0,sizeof(sum));
   for (i=1;i<=n;i++)
   {
	   cin>>a[i];
	   dp[i][i]=0;fp[i][i]=0;//一堆沒有合併,初始為1
	   sum[i]=sum[i-1]+a[i];
   }
   for (i=1;i<=n-1;i++)//將前n-1堆石子挪到第n堆後面
   {
	   dp[n+i][n+i]=0;fp[n+i][n+i]=0;
	   sum[n+i]=sum[n+i-1]+a[i];
   }
   for (len=2;len<=n;len++)//區間長度
	   for (i=1;i<=2*n-1;i++)
	   {
			j=len+i-1;
			if (j>2*n-1)	break;
			for (k=i;k<j;k++)//注意這裡k<j!!否則出現dp[i+1][i]的越界情況
			{
				dp[i][j]=max(dp[i][j],dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1]);
				fp[i][j]=min(fp[i][j],fp[i][k]+fp[k+1][j]+sum[j]-sum[i-1]);
			}
	   }
	int amin=2<<25,amax=0;
	for(i=1;i<=n;i++)//由於是環狀的任意一種起點終點都有可能
	{
		amin=min(amin,fp[i][i+n-1]);//注意是i+n-1!!
		amax=max(amax,dp[i][i+n-1]);
	}
	cout<<amin<<endl<<amax<<endl;
    return 0;
}

Brackets  括號匹配問題
題意:給出一個的只有'(',')','[',']'四種括號組成的字串,求最多有多少個括號滿足題目裡所描述的完全匹配。
·空串是完全匹配
·若s是完全匹配,則(s)和[s]是;若a和b是完全匹配,則ab是

思路:dp[i][j]表示[i,j]裡最大完全匹配數。

//不知道對不對,沒找到可以評測的oj,哪位小夥伴知道可以告知我一聲謝謝!!
#include <iostream>
#include<algorithm>
#include<string.h>
#include<string>
using namespace std;
int dp[101][101];
int main()
{
	string s;
	int len,j,k,i,n;
	while(cin>>s)
	{
		if (s=="end")
			break;
		n=s.length();
		s=" "+s;//s[1...n]
		memset(dp,0,sizeof(dp));
		for (len=2;len<=n;len++)
			for (i=1;i<=n;i++)
			{
				j=len+i-1;
				if (j>n)	break;
				if ((s[i]=='(' && s[j]==')') ||(s[i]=='[' && s[j]==']'))//若匹配
					dp[i][j]=dp[i+1][j-1]+2;
				for (k=i+1;k<j;k++)//其他博主寫的i~j-1,我覺得i+1開始也沒問題
					dp[i][j]=max(dp[i][j],dp[i][k]+dp[k+1][j]);//更新最優值
			}
		cout<<dp[1][n]<<endl;
	}
    return 0;
}

題目大意:找出以輸入序列為子序列的最短合法序列。如 ([(] 的答案是 ()[()]

思路解釋:思路參考
d[i][j]從i到j最少需要加多少括號才能成為合法序列。

c[i][j]為輸入序列從下標i到下標j的斷開位置,如果沒有斷開則為-1

程式碼理解有點困難,我舉個栗子:‘([))’,則dp[1][3]=1,dp[1][4]=2,c[1][4]=3,而dp[2][3]=2,即'[)',dp[1][4]==dp[2][3],則不會進入c[i][j]=-1。

而dp[1][3]在k的遍歷中得3,dp[2][2]=1<dp[1][3],此時進入c[i][j]==-1。所以僅當s[i]和s[j]是距離最近的匹配的括號時c[i][j]=-1,‘([))’中最左和最右不是匹配的,即不是最少的子問題

含義是,從內層開始找,最先找到的不匹配是解決的最小子問題(子問題解決了後面的不匹配也就解決了),即所求的最少的增加括號數

注意:
1、初始化dp[i][i]=1

2、匹配後加判斷dp[i][j]>dp[i+1][j-1]才能確定c[i][j]=-1

AC程式碼:

#include <iostream>
#include<string.h>
#include<string>
#define MAX 10000;
using namespace std;
string s;
int dp[101][101],c[101][101];
void print(int i,int j)
{
if (i>j)return ;
if (i==j && (s[i]=='(' || s[j]==')'))
{
cout<<"()";return;
}
else if (i==j &&(s[i]=='[' || s[j]==']') )
{
cout<<"[]";return;
}
else if (c[i][j]!=-1)
{
print(i,c[i][j]);
print(c[i][j]+1,j);
}
else//c[i][j]==-1,即s[i],s[j]是最小的完美匹配
{
if (s[i]=='(')
{
cout<<"(";print(i+1,j-1);cout<<")";//抵消(),輸出中間的
}
else
{ cout<<"[";print(i+1,j-1);cout<<"]";}
}
}
int main()
{
int len,j,k,i,n;
cin>>s;
n=s.length();
s=" "+s;
for (i=1;i<=n;i++)//初始化,每個單獨的括號都是1
dp[i][i]=1;
for (len=2;len<=n;len++)
for (i=1;i<=n;i++)
{
j=i+len-1;
if (j>n)break;
dp[i][j]=MAX;
for (k=i;k<j;k++)
if (dp[i][j]>dp[i][k]+dp[k+1][j])
{
dp[i][j]=dp[i][k]+dp[k+1][j];
c[i][j]=k;
}
if ((s[i]=='(' && s[j]==')') ||(s[i]=='[' && s[j]==']'))//若匹配
if (dp[i][j]>dp[i+1][j-1])//s[i],s[j]是匹配的最小子問題
{
dp[i][j]=dp[i+1][j-1];
c[i][j]=-1;
}
}
print(1,n);
cout<<endl;
    return 0;
}