poj1141

来源:互联网 发布:windows xp 原版 编辑:程序博客网 时间:2024/06/05 15:34

题目有难度。典型的dp中档偏下题。题目大意为:给定一个由’(‘、’)‘、’【’、‘】‘组成的串,要求从这些字符中选择某些字符插入到原串中,使之成为规格串。这里,规格串的定义如下:

1)空串

2)假设A为规格串,则(A)或者[A]也是规格串

3)假设A,B是规格串,则AB也是规格串

最开始的思路本来是模拟,利用堆栈,将左括号入栈,遇到有括号,检查栈顶左括号与之是否匹配,若匹配则出栈,否则添加一个与该有括号匹配的相应左括号,一直循环,直到串结束,然后检查堆栈是否为空,若不为空,则添加与左括号相匹配的右括号。这样运行后发现:结果比预期的最小串多了。说明这种模拟没有很好的与题目要求匹配。只能保证最后得出的一定是规格串,且包括原串,但不能保证一定是最小长度串。

这样,就尝试用dp。分析如下:

令dp[i][j]表示第i个字符到第j个字符要成为规格串所必须添加的最少字符数量。如何思考状态转移方程?根据题目规格串的特点,分析如下:

1)若第i个字符与第j个字符匹配,那么第i+1个字符到第j-1个字符要成为规格串所必需添加的最小字符数即dp[i+1][j-1]可能为dp[i][j]

2)  若不匹配,则dp[i][j]必定为min{dp[i][k]+dp[k+1][j]}, i<=k<j 即 第i个字符到第k个字符成为规格串添加的最小字符数+第k+1个字符到第j个字符必须添加的最小字符数  的最小值

同时令path[i][j]表示此时dp[i][j]取最小值时的k值(第2)中情况) 或最小值为第1)中情况取值,此时记path[i][j]为-1,以作区别。

这样就可以求解dp同时求解path.那么最后就是解决最短规格字符串如何输出问题了。

分析如下: 令Output(i,j)表示输出第i个字符到第j个字符的最小规格串。

有上述的dp分析可以知道当dp最小值取第1)种情况时,则可以知道第i个字符与第j个字符必定匹配,故可以直接输出第i个字符,调用Output(i+1,j-1)输出中间字符,然后输出第j个字符。而如果是第2)中情况。则可以分两次输出,分别为Output(i,path[i][j]) 和Output(path[i][j]+1,j) 而结束条件就是i==j的情况,直接输出字符与其匹配字符。

下面是代码: 228K+0MS

#include <stdio.h>#include <stdlib.h>#include <string.h>#define Inf 100000010 //定义无穷大#define Max 110 //串最大长度char str[Max]; // 记录串int dp[Max][Max]; int path[Max][Max];bool Is_match(char s,char t){ //判断字符是否匹配if(s=='(' && t==')' || s=='[' && t==']')return true;return false;}void Output(int left,int right){ // 输出最短规格串if(left>right) // 若left<right,则说明是两个字符相匹配后的调用结果,不做处理,直接返回return ;if(left==right){ //若为单个字符,则直接输出该字符与其匹配字符if(str[left]=='(' || str[left]==')'){putchar('(');    putchar(')');}else{putchar('[');    putchar(']');}return ;}if(path[left][right]==-1){ // 若最小值为第1)种情况,说明第i个字符与第j个字符必匹配,先输出第i个字符,递归输出中间字符,最后输出第j个字符putchar(str[left]);Output(left+1,right-1);putchar(str[right]);}else{  // 若最小值为第2)种情况,则先输出第i个字符到第k个字符的最小规格串,然后输出第k+1个字符到第j个字符的最小规格串int index=path[left][right];Output(left,index);Output(index+1,right);}}int main(){//scanf("%s",str);while(gets(str)!=NULL){  // 输入字符,注意要用gets,可能为空串情况,不能用scanfint len=strlen(str); int i,j,k;    memset(path,-1,sizeof(path)); // 初始化为-1for(i=0;i<len;i++){ dp[i][i]=1; // 初始化单个字符dp为1,最少添加1个字符dp[i+1][i]=0; // 为了后面的for循环的长度为2时的字符串dp计算使用,当两个字符匹配时就会使用到}int begin,end;for(i=2;i<=len;i++){ // 分别计算长度为2、3、……、len的各字符串dp值for(j=0;j<=len-i;j++){begin=j,end=j+i-1; //字符串起点下标,和终点下标if(Is_match(str[begin],str[end])) // 若匹配,赋初值,此时path[i][j]为-1                dp[begin][end]=dp[begin+1][end-1];else // 否则赋值无穷大dp[begin][end]=Inf;for(k=begin;k<end;k++){ // 枚举每种情况,取最小值,注意这里为<=,若为小于则效率会降低,AC时间为16MS,可以理解为第2)中情况为普遍情况,故应尽量想第2)种情况靠近,则在输出最短字符串时,处理起来一致性较好,时间较快if(dp[begin][k]+dp[k+1][end]<=dp[begin][end]){dp[begin][end]=dp[begin][k]+dp[k+1][end];path[begin][end]=k;}}}}Output(0,len-1); // 输出最短规格串printf("\n"); // 注意加空行符}return 0;}


 

 

 

0 0