【区间dp】P7914 [CSP-S 2021] 括号序列

题意

  1. ()、(S) 均是符合规范的超级括号序列,其中 S 表示任意一个仅由不超过kkk个字符∗*组成的非空字符串(以下两条规则中的 S 均为此含义);
  2. 如果字符串 A 和 B 均为符合规范的超级括号序列,那么字符串 AB、ASB 均为符合规范的超级括号序列,其中 AB 表示把字符串 A 和字符串 B 拼接在一起形成的字符串;
  3. 如果字符串 A 为符合规范的超级括号序列,那么字符串 (A)、(SA)、(AS) 均为符合规范的超级括号序列。

所有符合规范的超级括号序列均可通过上述 3 条规则得到。

现在给出一个长度为nnn的超级括号序列,一些位置的字符尚未确定(用 ? 表示)。求有多少种方法,使得得到的字符串是一个符合规范的超级括号序列?
1≤k≤n≤500。1 \le k \le n \le 500。1kn500

思路

fl,rf_{l,r}fl,rl∼rl \sim rlr符合规范的序列方案,Sl,rS_{l,r}Sl,rl∼rl \sim rlr是否可以拼成一个SSS
第一种情况以及第三种情况中的(A),直接判断两端是否可以为括号,中间是否可以为SSS
第三种情况,枚举断点即可。
对于第二种情况,需要枚举SSS,发现SSS的左端点是随着右端点向右移而右移的,可以用前缀和优化。

注意到ASASA,这种情况会被计算2遍,所以第二种分开计算即可。

时间复杂度O(N3)O(N^3)O(N3)

代码

#include <cstdio>
#define int long long

const int mod = 1e9 + 7;
int n, lim;
char s[511];
int f[511][511], S[511][511], g[511][511];

signed main() {
	scanf("%lld %lld", &n, &lim);
	scanf("%s", s + 1);
	for (int i = 1; i <= n; i++) {
		S[i][i - 1] = 1;
		for (int j = i; j - i + 1 <= lim && j <= n; j++)
			S[i][j] = S[i][j - 1] & (s[j] == '*' || s[j] == '?');
	}
	for (int i = n; i >= 1; i--)
		for (int j = i; j <= n; j++) {
			if ((s[i] == '(' || s[i] == '?') && (s[j] == ')' || s[j] == '?')) {
				(f[i][j] += f[i + 1][j - 1] + S[i + 1][j - 1]) %= mod;//第一种
				for (int k = i + 1; k < j - 1; k++)
					(f[i][j] += f[i + 1][k] * S[k + 1][j - 1] + f[k + 1][j - 1] * S[i + 1][k]) %= mod;//第三种
			}
			g[i][j] = f[i][j];//去重
			int sum = 0, l = i;
			for (int r = i + 1; r < j; r++) {//第二种
				(sum += f[i][r]) %= mod;
				while (!S[l][r]) {
					(sum -= f[i][l]) %= mod;
					l++;
				}
				(f[i][j] += (sum + f[i][l - 1]) * g[r + 1][j]) %= mod;
			}
		}
	printf("%lld", (f[1][n] + mod) % mod);
}
### 关于CSP-S 2021的相关题目及题解 #### 题目概述 CSP-S 2021 是中国计算机学会举办的全国青少年信息学奥林匹克竞赛的一部分,属于提高组的比赛。该赛事考察选手的算法设计能力以及编程实现技巧。以下是部分经典题目及其对应的题解。 --- #### **回文** ##### 简要题意 给定字符串 `s` 和若干次操作,每次操作可以删除某个字符或将某两个相邻字符交换位置。求最少的操作次数使得整个字符串成为回文串[^1]。 ##### 解决方案 此问题可以通过动态规划解决。定义状态 `dp[i][j]` 表示将子串 `s[i..j]` 转化为回文所需的最小操作数。转移方程如下: - 如果 `s[i] == s[j]`,则无需额外操作:`dp[i][j] = dp[i+1][j-1]` - 否则,取两种可能中的较小值:`dp[i][j] = min(dp[i+1][j], dp[i][j-1]) + 1` 最终答案即为 `dp[1][n]`,其中 `n` 是字符串长度。 ```cpp #include <iostream> #include <cstring> using namespace std; const int MAXN = 1005; int dp[MAXN][MAXN]; string s; int main() { cin >> s; memset(dp, 0x3f, sizeof(dp)); int n = s.size(); for (int i = 0; i < n; ++i) dp[i][i] = 0; for (int len = 2; len <= n; ++len) { for (int i = 0; i + len - 1 < n; ++i) { int j = i + len - 1; if (s[i] == s[j]) dp[i][j] = dp[i+1][j-1]; else dp[i][j] = min(dp[i+1][j], dp[i][j-1]) + 1; } } cout << dp[0][n-1] << endl; } ``` --- #### **括号序列** ##### 简要题意 给定一个由左括号 `'('` 和右括号 `')'` 组成的字符串,允许执行有限次翻转操作(即将某些位置上的括号替换为其相反形式),问至少需要多少次翻转才能使整个字符串合法[^4]。 ##### 解决方案 通过维护前缀和的方式记录当前未匹配的左括号数量,并利用贪心策略计算所需翻转次数。具体而言,当遇到无法配对的情况时立即进行一次翻转并更新计数器。 ```cpp #include <bits/stdc++.h> using namespace std; int main(){ string str; cin>>str; long long cnt=0,res=0; for(auto c:str){ if(c=='(')cnt++; else{ if(cnt>0)cnt--; else res++,cnt++; // Flip ')' } } cout<<res+(cnt/2); } ``` --- #### 总结与评价 根据过往参赛者的反馈,本次比赛整体难度适中,适合有一定基础的学生挑战自我[^2]。对于像《回文》这样的高分题,掌握核心概念即可轻松得分;而对于稍复杂的DP类问题,则需多加练习以提升建模能力和代码调试效率。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值