poj 1141 Brackets Sequence 题目链接:http://poj.org/problem?id=1141
DP
题目大意:一个字符串只有方括号和圆括号四种字符,规定合法序列,要求将输入序列修改成最短合法序列输出。
合法序列定义:
(1)空序列是一个合法的序列。
(2)如果S是一个合法序列,那么(S)和[S]都是合法的序列
(3)如果A和B都是合法序列,那么AB是合法序列。
题目分析:大部分注释在代码里,核心思想就是状态转移那部分,决策时涉及到二分。
code:
#include<cstdio>
#include<cstring>
#include<string>
#include<iostream>
using namespace std;
const int N=200;
string str;
int dp[N][N],path[N][N];
void oprint(int i,int j)
{
if(i>j)return;//无效位置返回
if(i==j)//子列仅含一个字符
{//那就把它自己匹配起来
if(str[i]=='['||str[i]==']')printf("[]");
else printf("()");
}
else if(path[i][j]==-1)//i、j可匹配
{
printf("%c",str[i]);//先输左括号
oprint(i+1,j-1);//再输中间部分
printf("%c",str[j]);//后输右括号
}
else
{//其他情况,左右两部分分别输出
oprint(i,path[i][j]);
oprint(path[i][j]+1,j);
}
}
int main()
{
while(getline(cin,str))
{
int n=str.size();
if(n==0)
{
cout<<endl;
continue;
}
memset(dp,0,sizeof(dp));
for(int i=0;i<n;i++)
dp[i][i]=1;//赋单括号的匹配数
for(int r=1;r<n;r++)//阶段:递推子序列的长度
{
for(int i=0;i<n-r;i++)//状态:枚举子序列的开始位置
{
int j=i+r;//根据起始位置、长度,算出结束位置
dp[i][j]=0x7fffffff;//状态转移方程初始化为无穷大
if((str[i]=='('&&str[j]==')')||(str[i]=='['&&str[j]==']'))
//若当前子列的最外层括号已匹配,则可说明字列中括号亦已匹配(!!)
{
dp[i][j]=dp[i+1][j-1];
path[i][j]=-1;
//continue;这句不能要啊,但是书上有!?
}
for(int k=i;k<j;k++)//枚举中间指针
{
if(dp[i][j]>dp[i][k]+dp[k+1][j])//找到最少可添加字符数
{
dp[i][j]=dp[i][k]+dp[k+1][j];
path[i][j]=k;//记下最优中间位置
}
}
}
}
oprint(0,n-1);//具体输出
cout<<endl;
}
return 0;
}
PS:第一道稍难的DP,思路、代码完全照书来的,但是收获不小,收获主要来自书上的坑。这本书自称教材,上面居然有如此明显的逻辑错误,直接导致我WA多次,让我又一次清醒地认识到ACM王者之路上的坎坷与艰辛绝对不容小视。因祸得福,书上的这一错误,也让我对这个题的整体结构有了更进一步的认识。
PPS:还有一点,这个题poj上用string能过,用char数组就不行,奇怪~
最后老生常谈:DP,就是状态转移,就是多看一步,就是整体最优。