最近看区间dp也看了好几题了,终于知道了大概的思路,当然只是大概……只会套模板,稍微变一变就GG了,还是没真正理解……
先上区间dp的超级简单好写的模板
for(l=0;l<n;l++) //枚举区间长度
for(i=0,j=i+l;j<n;j++) //枚举头尾指针
{ 如果可以更新或初始化,更新或初始化dp[i][j],
for(k=i;k<j;k++) //虽然我有种预感,有的时候这边可能会变形,不一定就是两个区间合并,不知道这种预感对不对
比较dp[i][j]和dp[i][k]+dp[k+1][j],如果能更新的话更新
}
关于区间dp的理解:dp[l][r]记录的是l区间到r区间的最优解,那么就要保证l~r之间的小区间也是最优解,这样才可以由子问题堆砌大问题,同时要保证子问题可以合并成大问题(上次陕师大校赛的那道蜘蛛纸牌一开始想的是就按给定的牌顺序进行区间dp,怎么都觉得两个子区间不能合并,还好有一神来解救我告诉我按自然顺序进行dp,真是智障了……)
放一道poj上的裸题
Time Limit: 1000MS | Memory Limit: 65536K | |||
Total Submissions: 29473 | Accepted: 8389 | Special Judge |
Description
Input
Output
Sample Input
([(]
Sample Output
()[()]
题意:给定原括号序列,让你添加最少的括号使其完全匹配,输出最小的匹配方案
思路:对于每个记录i~j最小添加次数的dp[i][j],如果i与k本身就匹配,那么dp[i][j]可以转变为dp[i+1][j-1](注意:这里转变后不一定是最优解,比如()()这样的,不转移不要添加,转移后就要添加两个了),接着进行小区间的枚举,如果dp[i][j]>dp[i][k]+dp[k+1][j]那么就进行更新。
上面这些没有难度,套模板就行了,难点是要输出匹配后的方案。
我们可以发现,对于每一组括号,总是先输出总是先输出外围的,再输出内部的,这不难想到进行递归(好吧,智障的我在看题解之前完全想不到)输出。我们可以在求dp数组时同时记录下每个区间的划分端点是什么,如果没有划分端点,那么这个区间就直接可以转为i+1~j-1区间,如果有划分端点,那么从端点处进行二分输出。
代码如下
#include<stdio.h> #include<string.h> #include<math.h> using namespace std; int i,j,k,m,n,l; char s[150]; int dp[120][120]; int path[120][120]; void output(int l,int r) { if(l>r)return; if(l==r) {if(s[r]=='('||s[r]==')')printf("()"); else printf("[]"); return; } else if(path[l][r]==-1) {printf("%c",s[l]); output(l+1,r-1); printf("%c",s[r]); } else { output(l,path[l][r]); output(path[l][r]+1,r); } return; } int main() { while(gets(s)) { n=strlen(s); memset(dp,0,sizeof(dp)); //注意,当区间长度为2时,()这样的dp[i][i+1]就正好赋值为0 for(i=0;i<=n;i++)dp[i][i]=1; for(l=1;l<n;l++) {for(i=0,j=i+l;j<n;i++,j++) { dp[i][j]=0x1f1f1f; if(s[i]=='(' && s[j]==')' || s[i]=='[' && s[j]==']' && dp[i][j]>dp[i+1][j-1]){dp[i][j]=dp[i+1][j-1];path[i][j]=-1;} for(k=i;k<j;k++) if(dp[i][k]+dp[k+1][j]<dp[i][j]) {dp[i][j]=dp[i][k]+dp[k+1][j]; path[i][j]=k; } } } //printf("%d\n",dp[0][n-1]); /*for(i=0;i<n;i++) for(j=0;j<n;j++) printf("i:%d j:%d :%d\n",i,j,dp[i][j]);*/ output(0,n-1); printf("\n"); } return 0; }