poj1141

11 篇文章 0 订阅

题目有难度。典型的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,可能为空串情况,不能用scanf
	int len=strlen(str); 
	int i,j,k;
    memset(path,-1,sizeof(path)); // 初始化为-1
	for(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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值