括号序列问题 uva 1626 poj 1141【区间dp】

首先考虑下面的问题:Code[VS] 3657

 我们用以下规则定义一个合法的括号序列:

    (1)空序列是合法的

    (2)假如S是一个合法的序列,则 (S) 和[S]都是合法的

    (3)假如A 和 B 都是合法的,那么AB和BA也是合法的

    例如以下是合法的括号序列:

      (), [], (()), ([]), ()[], ()[()]

    以下是不合法括号序列的:

      (, [, ], )(, ([]), ([()

  现在给定一些由'(', ')', '[', ,']'构成的序列 ,请添加尽量少的括号,得到一个合法的括号序列。

  输入包括号序列S。含最多100个字符(四种字符: '(', ')', '[' and ']') ,都放在一行,中间没有其他多余字符。

  使括号序列S成为合法序列需要添加最少的括号数量。

   样例输入 Sample Input

   ([()  

  样例输出 Sample Output

   2

  这是LRJ黑书上讲动态规划的第一道例题,我觉得不算简单>_<.,但让我明白了动态规划的两种动机:记忆化搜索和自底向上的递推。先说这道题的理解,黑书上设SiSi+1...Sj最少需要添加d[i,j]个括号。当S是'(S)'或'[S]'形式是很好理解,由于两端是对称的,那么我可以递归考虑里面的:d[i,j]=d[i+1,j-1]。当S是'(S'、'[S'、'S)'、'S]'等类似前面的道理,只不过考虑的分别是d[i,j]=d[i+1,j],d[i,j]=d[i,j-1]。其实让我迷惑的是最后还要把S分成两段Si....Sk,Sk+1...Sj分别求解再相加。后来看了别人博客的解释才明白些。因为S就可以只看成两类,两段匹配的和不匹配的,匹配的直接递归,不匹配的就分成两部分再求解。

      所以针对上面的问题,就有了两种dp写法。dp[i][j]表示i、j为开头结尾时需要添加的括号数。

  记忆化搜索:参考代码如下。黑书里面写的有我注释那那部分,但是按照上面的分析,其实直接分成dp[i][j]=dp[i+1][j-1]和dp[i][j]=dp[i][k]+dp[k+1][j]就可以了。但是我觉得那部分代码对我理解递归还是个很有帮助的,而且不注释好像更快些。Recursive is amazing!  

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 #include<algorithm>
 5 using namespace std;
 6 const int MAXN = 107;
 7 int dp[MAXN][MAXN];
 8 char s[MAXN];
 9 
10 int dfs(int i, int j)
11 {
12     if (dp[i][j] != -1) return dp[i][j];
13     if (i > j) return 0;
14     if (i == j) return 1;
15     int ans = 1e9;
16     if ((s[i] == '('&&s[j] == ')') || (s[i] == '['&&s[j] == ']'))
17         ans = min(ans, dfs(i + 1, j - 1));
18     /*else if (s[i] == '(' || s[i] == '[')
19         ans = min(ans, dfs(i + 1, j) + 1);
20     else if (s[j] == ')' || s[j] == ']')
21         ans = min(ans, dfs(i, j - 1) + 1);*/
22     for (int k = i; k < j; k++)
23         ans = min(ans, dfs(i, k) + dfs(k + 1, j));
24     return dp[i][j] = ans;
25 }
26 
27 int main()
28 {
29     while (scanf("%s",s)==1)
30     {
31         int len = strlen(s);
32         memset(dp, -1, sizeof(dp));
33         printf("%d\n", dfs(0, len - 1));
34     }
35 }

 

 自底向上的递推:其实看起来代码和上面的差不多。也是一样,去掉注释竟然会快些。

 

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 #include<algorithm>
 5 using namespace std;
 6 const int MAXN = 107;
 7 int dp[MAXN][MAXN];
 8 char s[MAXN];
 9 
10 int main()
11 {
12     while (scanf("%s",s)==1)
13     {
14         int len = strlen(s);
15         for (int i = 0; i < len; i++) {
16             dp[i][i] = 1, dp[i][i - 1] = 0;
17         }
18         for (int p = 1; p < len; p++)//p指的是i、j之间的距离
19         {
20             for (int i = 0; i + p < len; i++)
21             {
22                 int j = i + p;
23                 dp[i][j] = 1e9;
24                 if ((s[i] == '('&&s[j] == ')') || (s[i] == '['&&s[j] == ']'))
25                     dp[i][j] = dp[i + 1][j - 1];
26             /*    else if (s[i] == '(' || s[i] == '[')
27                     dp[i][j] = min(dp[i][j], dp[i + 1][j])+1;
28                 else if (s[j] == ')' || s[j] == ']')
29                     dp[i][j] = min(dp[i][j], dp[i][j - 1])+1;  */
30                 for (int k = i; k < j; k++)
31                     dp[i][j] = min(dp[i][j], dp[i][k]+dp[k+1][j]);
32             }
33         }
34         printf("%d\n", dp[0][len - 1]);
35     }
36     return 0;
37 }

 

下面的UVa 1626  poj 1141就是比上面的题多了个输出结果,这俩题一样,就是输入输出要求有点差别而已。需要特别注意的是,这两道题输入中都有空格,所以只能用gets()函数,我用scanf("%s")WA到死。。。(也没见题中说由空格啊!有空格还对吗?难道空格是在字符串开头?)

其实一看见让输出我是很懵逼的,这怎么输出。能求出最少添加数我就很开心了。下面说说自己的理解,别人的方法是递归输出。既然是递归输出,就先考虑一下边间,显然i>j时直接return.i==j时,是'('或')'输出'()'否则输出'[]',当i!=j时,若i,j两端点正好匹配,那就先输出左端点再递归输出i+1,j-1部分最后输出又端点,若是剩下的其他情况就像上面一样分成两部分判断继续递归。这里分成两部分后应该在哪里分开递归?自然是在更新dp[i][j]=dp[i][k]+dp[k+1][j]的地方,网上有人在dp时添加了另外一个数组记录这个位置,也有人没有添加,而是递归输出结果的时候再判断,我这里选择了第二种,代码看起来简洁些。

自底向上递推:为了方便把端点匹配的情况写成了一个函数。有一个点就是注释里的dp[i][j]==dp[i+1][j-1]不能少,否则会WA!感觉应该是虽然有可能i,j匹配,但这不是原序列中i、j对应的匹配,因为这时候是在递归,所以不加上会WA。估计我比赛的时候不会注意到这种细节。。。。但另开个数组记录就不用考虑了。

UVa 1626

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 #include<algorithm>
 5 using namespace std;
 6 const int MAXN=107;
 7 int dp[MAXN][MAXN];
 8 char s[MAXN];
 9 
10 bool Judge(int i,int j)
11 {
12     if(s[i]=='('&&s[j]==')') return 1;
13     if(s[i]=='['&&s[j]==']') return 1;
14     return 0;
15 }
16 
17 void Print(int i,int j)
18 {
19     if(i>j) return;
20     if(i==j){
21         if(s[i]=='('||s[j]==')') printf("()");
22         else printf("[]");
23         return;
24     }else if(Judge(i,j)&&dp[i][j]==dp[i+1][j-1]){//后面的判断条件不能省略
25         printf("%c",s[i]);
26         Print(i+1,j-1);
27         printf("%c",s[j]);
28         return;
29     }else for(int k=i;k<j;k++)
30     if(dp[i][j]==dp[i][k]+dp[k+1][j]){
31         Print(i,k);
32         Print(k+1,j);
33         return;
34     }
35 }
36 
37 int main()
38 {
39     int T;
40     scanf("%d",&T);
41     getchar();
42     while(T--)
43     {
44         gets(s);
45         gets(s);
46         int len=strlen(s);
47         memset(dp,0,sizeof(dp));
48         for(int i=0;i<len;i++){
49             dp[i][i]=1,dp[i][i-1]=0;
50         }
51         for(int p=1;p<len;p++)
52         {
53             for(int i=0;i+p<len;i++)
54             {
55                 int j=i+p;
56                 dp[i][j]=1e9;
57                 if(Judge(i,j))
58                     dp[i][j]=dp[i+1][j-1];
59                 for(int k=i;k<j;k++)
60                     dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]);
61             }
62         }
63         Print(0,len-1);
64         printf("\n");
65         if(T)
66             printf("\n");
67     }
68     return 0;
69 }

 

附带一个用flag[]数组标记的写法:

 

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 #include<algorithm>
 5 using namespace std;
 6 const int MAXN=107;
 7 int dp[MAXN][MAXN],flag[MAXN][MAXN];
 8 char s[MAXN];
 9 
10 bool Judge(int i,int j)
11 {
12     if(s[i]=='('&&s[j]==')') return 1;
13     if(s[i]=='['&&s[j]==']') return 1;
14     return 0;
15 }
16 
17 void Print(int i,int j)
18 {
19     if(i>j) return;
20     if(i==j){
21         if(s[i]=='('||s[j]==')') printf("()");
22         else printf("[]");
23         return;
24     }else if(flag[i][j]==-1){
25         printf("%c",s[i]);
26         Print(i+1,j-1);
27         printf("%c",s[j]);
28         return;
29     }else {
30         Print(i,flag[i][j]);
31         Print(flag[i][j]+1,j);
32     }
33 }
34 
35 int main()
36 {
37     int T;
38     scanf("%d",&T);
39     getchar();
40     while(T--)
41     {
42         gets(s);
43         gets(s);
44         int len=strlen(s);
45         memset(dp,0,sizeof(dp));
46         for(int i=0;i<len;i++){
47             dp[i][i]=1,dp[i][i-1]=0;
48         }
49         for(int p=1;p<len;p++)
50         {
51             for(int i=0;i+p<len;i++)
52             {
53                 int j=i+p;
54                 dp[i][j]=1e9;
55                 if(Judge(i,j)){
56                     dp[i][j]=dp[i+1][j-1];
57                     flag[i][j]=-1;
58                 }
59                 for(int k=i;k<j;k++){
60                     if(dp[i][j]>dp[i][k]+dp[k+1][j]){
61                         dp[i][j]=dp[i][k]+dp[k+1][j];
62                         flag[i][j]=k;
63                     }
64                 }
65             }
66         }
67         Print(0,len-1);
68         printf("\n");
69         if(T)
70             printf("\n");
71     }
72     return 0;
73 }
UVa 1626 用flag[]标记

 

poj 1141类似:

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 #include<algorithm>
 5 using namespace std;
 6 const int MAXN = 107;
 7 int dp[MAXN][MAXN];
 8 char s[MAXN];
 9 
10 bool Judge(int i, int j)
11 {
12     if (s[i] == '('&&s[j] == ')') return true;
13     if (s[i] == '['&&s[j] == ']') return true;
14     return false;
15 }
16 
17 void Print(int l, int r)
18 {
19     if (l > r) return;
20     if (l == r) {
21         if (s[l] == '(' || s[r] == ')')
22             printf("()");
23         else printf("[]");
24         return;
25     }
26     else if (Judge(l, r) && dp[l][r] == dp[l + 1][r - 1]) {
27         printf("%c", s[l]);
28         Print(l + 1, r - 1);
29         printf("%c", s[r]);
30     }else for(int k=l;k<r;k++)
31         if (dp[l][r] == dp[l][k] + dp[k + 1][r]) {
32             Print(l, k);
33             Print(k + 1, r);
34             break;
35         }
36 }
37 
38 int main()
39 {
40     while (gets(s))
41     {
42         int len = strlen(s);
43         memset(dp, 0, sizeof(dp));
44         for (int i = 0; i < len; i++) {
45             dp[i][i - 1] = 0, dp[i][i] = 1;
46         }
47         for (int p = 1; p < len; p++)
48         {
49             for (int i = 0; i < len - p; i++) {
50                 int j = i + p;
51                 dp[i][j] = 1e9;
52                 if ((s[i] == '('&&s[j] == ')') || (s[i] == '['&&s[j] == ']'))
53                     dp[i][j] = dp[i + 1][j - 1];
54                 for (int k = i; k < j; k++)
55                     dp[i][j] = min(dp[i][j], dp[i][k] + dp[k + 1][j]);
56             }
57         }
58         Print(0, len - 1);
59         printf("\n");
60     }
61     return 0;
62 }
自底向上递推poj1141

 

记忆化搜索也是类似的方法,比递推满了好多倍。。用flag[]数组标记比较好,不标记不知道怎么弄>_<。而且要像上面一样令int ans=1e9,最后再返回dp[i][j]=ans,直接令dp[i][j]=1e9会出错。。。先不深究了,这题写了一天。。。

 

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 #include<algorithm>
 5 using namespace std;
 6 const int MAXN = 107;
 7 int dp[MAXN][MAXN],flag[MAXN][MAXN];
 8 char s[MAXN];
 9 
10 bool Judge(int i, int j)
11 {
12     if (s[i] == '('&&s[j] == ')') return 1;
13     if (s[i] == '['&&s[j] == ']') return 1;
14     return 0;
15 }
16 
17 int dfs(int i, int j)
18 {
19     if (dp[i][j] != -1) return dp[i][j];
20     if (i > j) return 0;
21     if (i == j) return 1;
22     int ans = 1e9;
23     if (Judge(i,j)) {
24         ans = min(ans, dfs(i + 1, j - 1));
25         flag[i][j] = -1;
26     }
27     for (int k = i; k < j; k++) {
28         if (ans > dfs(i, k) + dfs(k + 1, j)) {
29             ans = dfs(i, k) + dfs(k + 1, j);
30             flag[i][j] = k;
31         }
32     }
33     return dp[i][j] = ans;
34 }
35 
36 void Print(int i, int j)
37 {
38     if (i>j) return;
39     if (i == j) {
40         if (s[i] == '(' || s[j] == ')') printf("()");
41         else printf("[]");
42         return;
43     }
44     else if (flag[i][j] == -1) {
45         printf("%c", s[i]);
46         Print(i + 1, j - 1);
47         printf("%c", s[j]);
48         return;
49     }
50     else {
51         Print(i, flag[i][j]);
52         Print(flag[i][j] + 1, j);
53     }
54 }
55 
56 int main()
57 {
58     int T;
59     scanf("%d", &T);
60     getchar();
61     while (T--)
62     {
63         gets(s);
64         gets(s);
65         int len = strlen(s);
66         memset(dp, -1, sizeof(dp));
67         dfs(0, len - 1);
68         Print(0, len - 1);
69         printf("\n");
70         if (T)
71             printf("\n");
72     }
73     return 0;
74 }
记忆化搜索Uva 1626

 

poj 1141的完全类似。。。

 

posted @ 2017-08-23 17:06  ╰追憶似水年華ぃ╮  阅读(264)  评论(0编辑  收藏  举报