看题
不是哥们怎么感觉一堆原题但是都不会做
没复习最悲惨的一次
策略肯定还是暴力, 没有什么看上去简单的题
\(\rm{T1}\)
思路
侥幸心理找了一下没有啊, 必须自己想
合法串显然就是满足匹配的串
考虑这种经典问题的常见转化 : 令 (
为 \(1\) , )
为 \(-1\) , 合法括号串仅当其任意前缀 \(\geq 0\) 且全串之和 \(= 0\)
先考虑确定 \(i\) 时怎么计算
首先计算 \(T [i : n]\) 中的前缀, 记录所有串和 \(\leq 0\) 的, \(> 0\) 的必定没有作用
然后考虑 \(T [1 : i]\) 中的后缀, 记录所有串和 \(\geq 0\) 的, \(< 0\) 的必定没有作用
有点感性, 但是应该是这样的
考虑答案合并
令 \(f_i\) 表示对于 \(T [i : n]\) 中的前缀, 所有串和 \(= -i\) 串串的个数, 令 \(g_i\) 表示对于 \(T [1 : i - 1]\) 中的后缀, 所有串和 \(= i\) 的串串的个数
容易发现 \(g_i, f_i\) 可以组成一个合法串
考虑方案数即为 \(f_i \times g_i\)
感觉就是这样, 手摸下样例
发现一些问题, \(f_i\) 还要新开一维表示串和, 但是显然是做不到的
令 \(f_{i, j}\) 表示对于所有以 \(i\) 开头的串串, 所有串和 \(= -j\) 串串的个数, 令 \(g_{i, j}\) 表示对于所有以 \(i\) 结尾的串串, 所有串和 \(= j\) 的串串的个数, 考虑怎么做到对于每一个 \(i\) , 快速算出 \(f, g\)
好消息是 \(\mathcal{O} (n^2)\) 能做了
考虑的更深一点, 其实可以对于全串的前后缀做处理进而找到 \(f, g\)
问题变成怎么做才能规避掉对前后缀串和的枚举
理一下
首先是一个基础:
对于 \(i\) 位置结果的统计, 你只需要考虑
- 以 \(i\) 结尾的后缀中, 串和 \(\geq 0\) 的
- 以 \(i\) 开头的前缀中, 串和 \(\leq 0\) 的
误区在于你还是要统计不满足考虑条件的作为转移, 不然会漏掉情况
那么具体的, 我们设计状态
令 \(f_{i, j}\) 表示以 \(i\) 结尾的后缀中, 串和为 \(j\) 的
令 \(g_{i, j}\) 表示以 \(i\) 开头的前缀中, 串和为 \(j\) 的
\(f, g\) 的状态转移是容易做到 \(\mathcal{O} (n)\) 的, 考虑 \(i\) 点的答案
容易做到分类讨论
- 前缀 + \(i\) + 后缀
- 前缀 + \(i\)
- \(i\) + 后缀
非常经典的分类, 以后一定要注意不要漏了
但是你发现瓶颈在枚举 \(j\) 上, 怎么办, 必须解决这个问题
这个可能要改一下状态, 或者整体处理
不太会了
实现 \(30 \rm{pts}\)
框架
如上讨论即可
代码
#include <bits/stdc++.h>
#define int long long
const int MAXN = 5206; //41
const int MOD = 1e9 + 7;
namespace calc {int add(int a, int b) { return a + b > MOD ? a + b - MOD : a + b; }int sub(int a, int b) { return a - b < 0 ? a - b + MOD : a - b; }int mul(int a, int b) { return (a * b * 1ll) % MOD; }void addon(int &a, int b) { a = add(a, b); }
} using namespace calc;int n;
std::string bra;
int a[MAXN];
std::map<int, int> f[MAXN], g[MAXN];signed main()
{std::cin >> bra; n = bra.length(); bra = ' ' + bra;for (int i = 1; i <= n; i++) a[i] = (bra[i] == '(') ? 1 : -1, f[i][a[i]] = 1, g[i][a[i]] = 1;/*转移*/for (int i = 1; i <= n; i++) for (int j = -n; j <= n; j++) addon(f[i][j], f[i - 1][j - a[i]]);for (int i = n; i >= 1; i--) for (int j = -n; j <= n; j++) addon(g[i][j], g[i + 1][j - a[i]]);int ans = 0;/*统计答案*/for (int i = 1; i <= n; i++) {int res = 0;for (int j = ((a[i] == 1) ? -1 : 1); j <= n; j++) addon(res, mul(f[i - 1][j], g[i + 1][-j - a[i]]));if (a[i] == 1) addon(res, g[i + 1][-a[i]]);if (a[i] == -1) addon(res, f[i - 1][-a[i]]);addon(ans, mul(res, i));}printf("%lld", ans);return 0;
}
\(\rm{T2}\)
\(\rm{T1}\) 想乱了, 先去想 \(\rm{T2}\) , 回头再看 \(\rm{T1}\)
甚至 \(\rm{T2}\) 也像做过的题
很危险, 赶紧把该拿的分拿了, 不要在死磕
先不管 \(\rm{T1}\) 了
今天的策略是把后面的题暴力打了死磕 \(\rm{T1}\)
思路
这个题绝对是做过的
原来至少三位数是这样的吗
好吧那不管这道题
\(\rm{T3}\)
目前得分: \(130 \ \rm{pts}\)
虽然那 \(100\) 应该不能算
思路
首先把每一行删除一位之后结果相同的删除选择并到一起
具体的, 如果删除 \(a_i, a_j\) 对于 \(i\) 行的结果一样, 那么把 \(i, j\) 加入同一个集合, 假设第 \(i\) 行对应的操作集合为 \(\mathbb{S}_{i, k}\)
考虑 \(\rm{dp}\) , 令 \(f_{i, j}\) 表示考虑到第 \(i\) 行, 其中这一行删除的位置属于 \(j\) 集合的方案数
考虑转移
其中并集特殊实现
复杂度 \(\mathcal{O} (n^3)\) , 完全足够了, 开打
总结
\(\rm{T1}\) 思路不够优秀, 没有意识到这一点浪费了太多时间
\(\rm{T3}, \rm{T4}\) 部分分很多, 没有意识到这一点
以后一定要对自己的水平有清楚认知, 做好策略, 然后按照时间分配打下去, 切莫死磕