黑书01 - POJ 1141 dp

姐对于dp是一点都不会啊,city说不能只局限于线段树,于是,我就选择了dp。因为这个不用学什么其他的算法大笑,而且可以问city。

既然开始学的话呢,就从黑书开始学吧。

黑书给我的要求是:

        例题必须完全掌握,练习也很重要。学习完之后,应该对于每一道题目都能在短时间内把思路完整地整理一遍。对于大部分题目,应当很快地写成程序。

于是,既然自己现在啥都不懂,就照着书的例题和要求来。相信做完之后一定会有所收获的!!!吐舌头

这是黑书的第一道例题,用来阐述动态规划的第一种动机-------利用递归的重叠子问题,进行记忆化求解

http://poj.org/problem?id=1141

讲得好:

题目大意:给你一贯括号序列(只包含小括号和中括号),让你找出长度最小的regular brackets sequence包含此子序列.其中的regular brackets sequence定义如下:

1)空序列是一个regular brackets sequence;

2)如果s是一个regular brackets sequence,那么[s] 也是一个regular brackets sequence,(s)也是一个regular brackets sequence.

3)如果A,B都是regular brackets sequence,那么AB也是一个regular brackets sequence.

例如:()、[]、()[] 、([]) 、([])()[()]都是regular brackets sequence。

而[[[、 (((((、 ([)] 则都不是regular brackets sequence。其中以“([)]”为例,包含它最小的regular brackets sequence有两个:()[()]或者是([])[].而你只要输出其中一个就行。

好了,题目题意讲完了,应该理解了吧。下面开始分析:

这个问题一眼就可以看出是DP题,为什么呢?因为很明显这个问题可以根据它定义中的(2)和(3)这两条性质划分出更小的子问题。也就是说,一个序列如果 是AB形式的话,我们可以划分为A,B两个子问题;而如果序列是[A]或者(A)的形式,我们可以把它降为分析A即可。分解的底层就是剩下一对[]或者 ()或者是只剩下一个单字符就停下不再分解。当剩下的是一对匹配的()或者[]时,我们不必添加如何括号,因为这已经匹配,而对于只剩下最后一个单字符, 我们需要对它配一个字符,使它配对,如(就配上),]就配上[,依此类推。

那么这题的状态转移方程就很容易列出来了,用a[i,j]表示从位置i到位置j所需要插入的最小字符数,明显有状态转移方程如下:

a[i,j]=min(a[i,k]+a[k+1,j]) 其中i<=k<j;这个是利用了定义的性质(3),枚举K,尝试所有的可能分解,取最优分解;

特别的,当a[i,j]的首尾为()或者[]时,

a[i,j] = min(a[i+1,j-1],tmp) 其中tmp为上面根据性质3求得的最小值,这条转移是利用了性质2。

初始条件为:

a[i,i]=1,表示任意一个字符都要一个对应的字符来匹配;

a[i+1,i]=0.这个没有什么实际的意义,只是前面的分析说了,当剩下一对()或者[]时,就不再继续往下分解,而我们为了更方便的组织程 序,把当剩下一对()或者[]时还继续分解,那么,举例子来说,本来序列为(),a[0,1]通过转移变成a[1,0],为了不出错,所以我们把a[i+ 1,i]初始化为0,这样组织程序起来也就比较容易了。

到这里,转移方程就结束了,如果这题只让你求最少需要插入的字符数,那么这题就结束了,而这题让你求的是包含子序列的最小regular brackets sequence,所以我们还需要对前面的求解过程进行标记,把每次求得最小值所取的位置都记录下来,然后用递归回溯的方法去求得最小的regular brackets sequence

如:我们用tag[i,j]表示i到j位置中记录下来该到哪里划分,假设初始化为-1,

如果a[i,j]选择最优的时候,选择的是a[i,k]+a[k+1,j],那么记录下k的位置;

如果a[i,j]选择的是a[i+1,j-1]的话,那么保持初始值即可。

这样再根据a[0,strlen(str)-1]逐步回溯。

/*
Pro: 0

Sol:

date:
*/
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <queue>
#include <set>
#include <vector>
#define inf 1000000000
using namespace std;
int d[111][111],len,path[111][111];
char s[111];
void output(int i, int j){
    if(i > j) return ;
    if(i == j) {
        if(s[i] == '(' || s[i] == ')') printf("()");
        else if(s[i] == '[' || s[i] == ']') printf("[]");
    }else if(path[i][j] == -1 ){
        printf("%c",s[i]);
        output(i + 1, j - 1);
        printf("%c",s[j]);
    }
//    else if(path[i][j] == 1){
//        if(s[i] == '('){
//           printf("%c",s[i]);
//           output(i + 1, j);
//           printf(")");
//        }else if(s[i] == '['){
//           printf("%c",s[i]);
//           output(i + 1, j);
//           printf("]");
//        }
//    }else if(path[i][j] == 2){
//        if(s[i] == ')'){
//           printf("(");
//           output(i, j - 1);
//           printf("%c",s[j]);
//        }else if(s[i] == '['){
//           printf("[");
//           output(i, j - 1);
//           printf("%c",s[j]);
//        }
//    }
    else{
        output(i,path[i][j]);
        output(path[i][j] + 1,j);
    }
}
int main(){
    scanf("%s",s + 1);
    len = strlen(s + 1);
    for(int i = 1; i <= len;i ++)
        d[i][i - 1] = 0;
    for(int i = 1; i <= len; i ++)
        d[i][i] = 1;//任何一个字符都需要另外一个字符来匹配
    memset(path,0,sizeof(path));
    for(int p = 1; p <= len - 1; p ++){//枚举长度
        for(int i = 1; i <= len - p; i ++){//枚举起始位置
            int j = i + p;
            d[i][j] = inf;
            if( (s[i]== '(' && s[j] == ')' )|| (s[i] == '[' && s[j] == ']'))
                if(d[i][j] > d[i + 1][j - 1])
                    d[i][j] = d[i + 1][j - 1],path[i][j] = -1;
//以下这段有重复,所以不要。
//            if( (s[i] == '(' && s[j] != ')')||( s[i] == '['&& s[j] != ']'))
//                if(d[i][j] > d[i + 1][j])
//                    d[i][j] = min(d[i][j],d[i + 1][j]),path[i][j] = 1;
//            if( (s[j] == ')' && s[j] != ')')||( s[j] == ']'&& s[j] == '['))
//                if(d[i][j] > d[i][j - 1])
//                    d[i][j] = min(d[i][j],d[i][j - 1]),path[i][j] = 2;

            for(int k = i; k <= j - 1; k ++)
                if(d[i][j] > d[i][k] + d[k + 1][j])
                    d[i][j] = d[i][k] + d[k + 1][j],path[i][j] = k;
        }
    }
    output(1,len);
    printf("\n");
	return 0;
}



你可能感兴趣的:(算法)