1.12 CW 模拟赛 T1. 括号序列
思路
根据赛时的检验,
典型的动点问题的 \(\rm{trick}\) 并不能在这里使用, 也就是说, 分类讨论
- 前缀 + \(i\) + 后缀
- 前缀 + \(i\)
- 后缀 + \(i\)
是不可行的
考虑括号串问题的常见做法, 先将其赋值成 \(1, -1\) 之后进行处理
你发现这种做法有枚举字段和的瓶颈, 所以也不可行
当然你可以进行转化之后利用这种做法
用栈来处理更是天方夜谭
考虑转到图上去处理, 容易的, 你可以将一个合法括号串表示成这样

你发现令 \(f_i\) 表示 \(i\) 节点的答案, 容易有
\[f_i = f_{\textrm{fa}_ \textrm{i}} + (lb_i + 1) \times (rb_i + 1)
\]
其中 \(lb, rb\) 表示左右兄弟的个数
容易转移
考虑建树怎么建
你发现不用真的建树, 你只需要把 \(lb, rb\) 求出来即可, 而这个你只需要在原串上直接做即可
具体怎么做呢, 可以见代码, 不想再描述了
很高端, 所以重新想一遍, 上一次写的略微屎了一点
题意
给定括号序列
要求对于每个位置 , 有多少个合法括号子串包含它
不难想到, 对于一个合法括号区间 \([L, R]\) , 其对于 \(i \in [L, R]\) 都有贡献
考虑类似扫描线, 从右往左枚举一个合法括号区间的左端点 \(L\) , 求有多少个 \(R\) 满足合法性
考虑括号树的思想, 把同级的括号提出来, 那么显然可以知道 \(ans_L = ans_{R' + 1} + 1\) , 然后简单递推即可
实现
框架
如上维护
代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN = 10000010, mod = 1e9 + 7;
int n, to[MAXN], stk[MAXN], tp, ans[MAXN], rans[MAXN]; ll res;
char s[MAXN];
int pre[MAXN];
signed main () {
scanf("%s", s + 1), n = strlen (s + 1);
for (int i = 1; i <= n; i++) {
if (s[i] == '(') stk[++tp] = i;
else if (tp) {
to[stk[tp]] = i, to[i] = stk[tp], tp--;
if (tp) pre[to[i]] = stk[tp];
}
}
for (int i = n; i >= 1; i--) if (to[i]) {
if (s[i] == '(') ans[i] = ans[to[i] + 1] + 1;
}
for (int i = 1; i <= n; i++) if (to[i]) {
if (s[i] == ')') rans[i] = rans[to[i] - 1] + 1;
}
for (int i = 1; i <= n; i++) if (to[i]) {
if (s[i] == '(') { ans[i] = ans[to[i]] = ans[pre[i]] + 1ll * ans[i] * rans[to[i]] % mod; }
res += (1ll * i * ans[i]) % mod;
}
printf("%lld", res);
return 0;
}
总结
想的其实挺有逻辑的, 但是确实不会这种情况下的问题
常用的方法, 把匹配类问题丢到图 / 树上去做
本质上其实是因为这个题中, 包含关系的括号串对答案是有继承关系的, 这种继承关系可以建成树来处理
不好建树考虑只利用思想, 简化计算

浙公网安备 33010602011771号