poj 1411 Brackets Sequence

题意:给定一串括号序列,找出包含此序列的长度最小的regularbrackets sequence,就是找出使每一对括号都成对出现的长度最小的序列

分析:此问题可根据(2)(3)划分出更小的子序列

http://hi.baidu.com/tifctu/blog/item/d7bbaadc40bbbf335882ddc4.html

一个序列如果是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,这样组织程序起来也就比较容易了。

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


#include<stdio.h>
#include<string.h>
#define min(a,b) a<b?a:b
int is_ok(char c,char d)
{
	if(c=='('&&d==')'||c=='['&&d==']')
	return 1;
	else 
	return 0;
}
char a[1010];
int d[1010][1010];
int mid[1010][1010];
void prit_ans(int L,int r)
{
	int i;
	if(L>r)
	return ;
	if(L==r)
	{
		if(a[L]=='('||a[L]=='['){
			printf("%c",a[L]);
			if(a[L]=='(')
			printf(")");
			else if(a[L]=='[')
			printf("]");
			return ;
		}
		else 
		{
			if(a[L]==')')
			printf("(");
			else if(a[L]==']')
			printf("[");
			printf("%c",a[L]);
			return ;
		}
		
	}
	if(mid[L][r]==-1)
	{
		for(i=L;i<=r;i++)
		printf("%c",a[i]);
	}
	else if(mid[L][r]==-2)
	{
		printf("%c",a[L]);
		prit_ans(L+1,r-1);
		printf("%c",a[r]);
	}
	else 
	{
		prit_ans(L,mid[L][r]);
		prit_ans(mid[L][r]+1,r);
	}
}
int main()
{
	int i,j,k,len,p,t;
	while(gets(a)!=NULL)
	{
		if(strlen(a)==0)
		puts(a);
		len=strlen(a);
		memset(d,0,sizeof(d));
		for(i=0;i<len;i++)
		{
			for(j=i+1;j<len;j++)
			d[i][j]=20000;
		}
		memset(mid,-1,sizeof(mid));
		for(i=0;i<len-1;i++)
		{
			d[i][i]=1;
			if(is_ok(a[i],a[i+1]))
			{
				d[i][i+1]=0;
			}
		}
		d[i][i]=1;
		for(p=1;p<len;p++)
		{
			for(i=0;i<len;i++)
			{
				j=i+p;
				if(j>len)
				break;
				for(k=i;k<j;k++)
				{
					if(d[i][j]>d[i][k]+d[k+1][j])
					{
						d[i][j]=d[i][k]+d[k+1][j];
						mid[i][j]=k;
					}
				}
				for(k=i+1;k<=j;k++)
				{
					if(is_ok(a[i],a[k]))
					{
						if(d[i][j]>d[i+1][k-1]+d[k+1][j])
						{
							d[i][j]=d[i+1][k-1]+d[k+1][j];
							mid[i][j]=-2;	
						}
						
					}
				}
			}
		}	
		//printf("%d\n",d[0][]);
		prit_ans(0,len-1);
		printf("\n");
		//printf("%d\n",d[46][3]);
	}
}

还有一种更简洁的代码:

#include <iostream>
#include <stdio.h>
#include <string.h>
using namespace std;
const int maxn=220;
const int inf=0x7fffffff;
int pos[maxn][maxn];//从i到j在哪里分开
int dp[maxn][maxn];//从i到j至少添几个符号
char str[maxn];
int len;

void print(int i,int j)
{
    if(i>j)
        return ;//递归出口
    if(i==j)
    {
        if(str[i]=='('||str[i]==')')
            cout<<"()";
        else
            cout<<"[]";
    }
    else if(pos[i][j]==-1)//两边是对称的
    {
        cout<<str[i];
        print(i+1,j-1);
        cout<<str[j];
    }
    else//可分割
    {
        print(i,pos[i][j]);
        print(pos[i][j]+1,j);
    }
}

int main()
{
    cin>>str;
    len=strlen(str);
    memset(dp,0,sizeof(dp));
    for(int i=0;i<len;i++)
        dp[i][i]=1;
    for(int k=1;k<len;k++)//长度
        for(int i=0;i+k<len;i++)//起点
        {
            int j=i+k;
            dp[i][j]=inf;
            if((str[i]=='('&&str[j]==')')||(str[i]=='['&&str[j]==']'))
            {
                dp[i][j]=dp[i+1][j-1];
                pos[i][j]=-1;//暂时让它等于-1
            }
            for(int mid=i;mid<j;mid++)//这个必须要执行的。
            {
                if(dp[i][j]>(dp[i][mid]+dp[mid+1][j]))
                {
                    dp[i][j]=dp[i][mid]+dp[mid+1][j];
                    pos[i][j]=mid;
                }
            }
        }
    print(0,len-1);
    cout<<endl;
    return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值