第十二届蓝桥杯省赛第一场C++B组真题(括号序列)

第十二届蓝桥杯省赛第一场C++B组真题(括号序列)_第1张图片

这道题目是第十二届蓝桥杯省赛的最后一题,我认为这道题目难度还是非常大的,现在来详细说一下这道题目的做法。

首先来说一下什么样的括号序列才是合法的括号序列,需要具备以下两个条件:

(1)左括号数目和右括号数目相同

(2)任何一个位置及之前的所有括号中,左括号数目大于等于右括号数目

我们要想使得给定的括号序列变得合法,我们可能需要向其中一些位置加入相应括号,可能是左括号也可能是右括号,题目中要求的是所有可能的合法方案数,那我们现在就需要思考一个问题了,我们加入的左括号和加入的右括号之间会不会相互影响?显然我们加入的括号是要放在原给定括号序列两个相邻的括号之间的,如果我们要加入的左括号和右括号放在原给定括号序列不同位置的两个相邻括号之间那么他们之间一定是不会相互影响的,那如果我们要加入的左括号和右括号放在原给定括号序列相同位置的两个相邻括号之间那么他们会有几种可能的插入方式呢?不妨我们要在同一个相邻括号之间插入两个左括号和两个右括号,他们之间的排列方式会不会是(())呢?显然是不会的,这样的话这两个括号直接就抵消了,相当于没加,而题目中说的是尽可能少地添加若干括号使得括号序列变得合法,所以显然不能有这种结果,那会不会是)(()呢?答案是也不会,因为这种排列方式也会在他们之间产生一个(),这样也会有多余的加入括号操作,所以他们之间只能是右括号在左,左括号在右的排列方式,也就是))((这样的排列方式。知道了左括号和右括号之间是相互独立的,那我们求答案的方案数就可以求出左括号可能的方案数和右括号可能的方案数然后求个乘积就可以了。

下面我们开始进行dp求解:

dp[i][j]表示序列前i个括号中左括号比右括号多j个方案数。需要记住的是我们只会往括号序列中插入左括号,因为我们判断一个序列不合法当且仅当此时左括号数目小于右括号数目。如果当前遍历到第i个位置,这个时候有两种情况,一种是(,另一种是),先来说一下左括号的情况,显然有dp[i][j]=dp[i-1][j-1],也就是已知第i个括号为左括号,则前i个括号中左括号比右括号数目多j个的方案数就等于前i-1个括号中左括号比右括号数目多j-1个的方案数,因为第i个括号是左括号,所以前i个括号中左括号比右括号数目多j个。下面来说一下第i个括号为右括号的情况,那么显然有dp[i][j]=dp[i-1][j+1]+dp[i-1][j]+……+dp[i-1][0],dp[i-1][j+1]代表我们在第i-1个括号和第i个括号之间不插入左括号,这个时候前i-1个序列中左括号比右括号多了j+1,那么算上第i个括号为右括号,那么前i个序列中左括号比右括号多了j,而dp[i-1][j]代表我们在第i-1个括号和第i个括号之间插入1个左括号,这个时候前i-1个序列中左括号比右括号多了j,那么算上第i个括号为右括号,再算上第i-1个括号和第i个括号之间插入的一个左括号,那么前i个序列中左括号比右括号多了j,其他的也是同理分析。但是我们并不能直接用上述公式进行递推,这样会有三重循环,我们需要对这个递推公式进行简化,分析可以发现dp[i][j-1]=dp[i-1][j]+dp[i-1][j-1]+……+dp[i-1][0],将这个等式代入上面等式可得

dp[i][j]=dp[i-1][j+1]+dp[i][j-1]

利用这个递推式就可以o(n)递推了,但是需要注意的一点是当j等于0时需要特殊处理一下,因为j-1=-1,所以我们不能用化简后的递推式去计算,只能用化简前的递推式去计算,即dp[i][0]=dp[i-1][1]+dp[i-1][0]

还有一个技巧就是当我们需要算插入右括号的合法序列时,我们可以将原序列逆置,然后把左括号变为右括号,把右括号变为左括号,还利用同样的函数即可,细节可以参考代码:

#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
const int N=5003,mod=1e9+7;
int n;
char s[N];
long long f[N][N];//f[i][j]表示前i个序列中左括号比右括号多j个的合法方案数 
long long cal()//计算插入括号合法方案数 
{
	memset(f,0,sizeof f);
	f[0][0]=1;//初始化
	for(int i=1;i<=n;i++)
	{
		if(s[i]=='(') 
		{
			for(int j=1;j<=n;j++)
				f[i][j]=f[i-1][j-1];
		}
		else
		{
			f[i][0]=(f[i-1][0]+f[i-1][1])%mod;//注意0的时候需要特殊处理一下,因为j-1=-1,所以我们不能用化简后的递推式去计算,只能用化简前的递推式去计算
			for(int j=1;j<=n;j++)
				f[i][j]=(f[i-1][j+1]+f[i][j-1])%mod;
		}
		for(int i=0;i<=n;i++)//答案要求我们插入的括号数尽量小,所以要从小向大枚举,右括号的数目是不变的,当加入尽量小的左括号且有解时就返回即可 
			if(f[n][i]) return f[n][i];
	} 
}
int main()
{
	scanf("%s",s+1);
	n=strlen(s+1);
	long long ans1=cal();//记录左括号的插入方案数 
	reverse(s+1,s+n+1);//将原序列逆置
	for(int i=1;i<=n;i++)
		if(s[i]=='(') s[i]=')';
		else s[i]='(';
	long long ans2=cal();//记录右括号的插入方案数
	printf("%lld",ans1*ans2%mod);
	return 0; 
}

你可能感兴趣的:(蓝桥杯备考,蓝桥杯)