括號匹配問題(區間dp)
簡單的檢查括號是否配對正確使用的是棧模擬,這個不必再說,現在將這個問題改變一下:如果給出一個括號序列,問需要把他補全成合法最少需要多少步?
這是一個區間dp問題,我們可以利用區間dp來解決,直接看代碼吧!
1 /* *********************************************** 2 Author :xiaowuga 3 Created Time :2017年10月03日 星期二 14時20分04秒 4 File Name :Desktop/text.cpp 5 *************************************************/ 6 #include <bits/stdc++.h> 7 #define mem(s,ch) memset(s,ch,sizeof(s)) 8 typedef long long LL; 9 #define inf 0x3f3f3f3f 10 const long long N=1000000; 11 const long long mod=1e9+7; 12 #define endl ‘\n‘ 13 using namespace std; 14 bool check(char a,char b){ 15 if((a==‘(‘&&b==‘)‘)||(a==‘[‘&&b==‘]‘)) return true; 16 else return false; 17 } 18 int dp[101][101]; 19 string q; 20 int main(){ 21 ios::sync_with_stdio(false);cin.tie(0); 22 int n; 23 cin>>n; 24 while(n--){ 25 cin>>q; 26 int len=q.size(); 27 memset(dp,0,sizeof(dp));28 for(int i=0;i<len;i++) dp[i][i]=1; 29 for(int i=len-2;i>=0;i--){ 30 for(int j=i+1;j<len;j++){ 31 dp[i][j]=inf; 32 if(check(q[i],q[j])) dp[i][j]=dp[i+1][j-1]; 33 for(int k=i;k<j;k++){ 34 dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]); 35 } 36 } 37 } 38 cout<<dp[0][len-1]<<endl; 39 } 40 return 0; 41 }
雖然也有記憶化搜索的做法,但是刷表的方法似乎代碼量更短,區間dp的刷表有一個特點i和j總是反過來,這是為什麽呢?由於區間dp中大區間的答案依賴於小區間,所以我們在更新大區間答案的時候一定要保證向他發生狀態轉移的小區間都已經得出了答案。所以:
for(int i=len-2;i>=0;i--){
for(int j=i;j<len;j++){
}
}
自己枚舉一下更新區間的順序就會發現,當我們需要更新區間dp[i][j]的答案的時候dp[i][k-1]和dp[k+1][j]都已經更新了。
現在來解釋一下:兩種轉移方式
1.如果q[i]和q[j]是可以匹配的,dp[i][j]=dp[i-1][j-1](顯然不解釋了)
2.對於一個區間,我們考慮把他分解成兩個子區間,那麽如果得到最優的子區間分配呢?答案是枚舉,枚舉所有的分配方式即
<[i,i],[i+1][j]>,<[i,i+1],[i+2,j]>,<[i,i+2],[i+3,j]>,<[i,i+3],[i+4,j]>,…………<[i,j-1],[j,j]>,找到所有分配方式最小值。
對於每一個區間我們找以上兩種轉移方式的最小值。
如果要打印打印方案(代碼):
1 void print(int l,int r){ 2 if(l>r) return; 3 if(l==r){ 4 if(q[l]==‘(‘||q[l]==‘)‘) cout<<"()"; 5 else if(q[l]==‘[‘||q[l]==‘]‘) cout<<"[]"; 6 return ; 7 } 8 if(check(q[l],q[r])&&dp[l][r]==dp[l+1][r-1]){ 9 cout<<q[l]; 10 print(l+1,r-1); 11 cout<<q[r]; 12 return ; 13 } 14 for(int k=l;k<r;k++){ 15 if(dp[l][r]==dp[l][k]+dp[k+1][r]){ 16 print(l,k); 17 print(k+1,r); 18 return ; 19 } 20 } 21 }
這個這裏相當於一個逆過程,如果你看了記憶化搜索代碼應該就會很好理解這個代碼了
記憶化搜索:
1 int dp(int x,int y){ 2 int &ans=d[x][y]; 3 if(x>y) return 0; 4 if(ans>0||x==y) return ans; 5 ans=n; 6 if(match(str[x],str[y])) ans=min(ans,dp(x+1,y-1)); 7 for(int i=x;i<y;i++){ 8 ans=min(ans,dp(x,i)+dp(i+1,y)); 9 } 10 return ans; 11 }
練習題:
http://acm.nyist.net/JudgeOnline/problem.php?pid=15
https://vjudge.net/problem/UVA-1626
括號匹配問題(區間dp)