【模板題】動態規劃 石子合併、括號匹配、加分二叉樹——區間dp問題及其整理
阿新 • • 發佈:2019-02-10
題目大意:輸入一棵樹的中序遍歷,定義一棵子樹的得分為其左子樹的加分×右子樹的加分+根的分數。求最大得分及先序遍歷
注意: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;
}