2025.1.12 CW 模拟赛

题面 & 题解

T1

思路

首先可以处理出每对括号的位置.

考虑怎么处理每对括号. 对于相邻的几对括号中的任意一对, 它可以选择的方案数为 \((左侧对数 + 1) \times (右侧对数 + 1)\). 那么我们只需要从左到右扫一遍, 记录一下每个连通块内的括号数量即可.

在实现上, 因为需要做区间加法, 我们使用差分数组记录. 再在统计答案的时候做一遍前缀和就行了.

时间复杂度 \(\mathcal{O}(n)\).

复制代码
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
#include "iostream" #include "numeric" #include "stack" #include "cstring" using namespace std; typedef pair<int, int> pii; constexpr int N = 1e7 + 10, mod = 1e9 + 7; int len, fa[N]; basic_string<pii> v; string s; stack<int> st; void init() { cin >> s; len = s.size(), s = ' ' + s; iota(fa, fa + len + 1, 0); for (int i = 1; i <= len; ++i) { if (s[i] == ')' and !st.empty()) v.push_back({st.top(), i}), st.pop(); else if (s[i] == '(') st.push(i); } } long long ans[N]; int cnt[N], pre[N], to_l[N]; void calculate() { for (auto [l, r] : v) if (to_l[(to_l[r] = l) - 1]) fa[l] = fa[to_l[l - 1]]; int sz = v.size(); for (int i = 0; i ^ sz; ++i) pre[i] = ++cnt[fa[v[i].first]]; for (int i = 0; i ^ sz; ++i) { int l = v[i].first, r = v[i].second; ans[l] += 1ll * pre[i] * (cnt[fa[l]] - pre[i] + 1), ans[r + 1] -= 1ll * pre[i] * (cnt[fa[l]] - pre[i] + 1); } long long tot = 0; for (int i = 1; i <= len; ++i) { ans[i] += ans[i - 1]; tot += 1ll * ans[i] * i % mod; } cout << tot << '\n'; } void solve() { init(); calculate(); } signed main() { cin.tie(nullptr)->sync_with_stdio(false); solve(); return 0; }

T2

算法

动态规划, 数学.

思路

容易想到令 \(f_{i, 0/1}\) 表示到第 \(i\) 位的时候该段是否是倍数串的答案.

瓶颈在于找到倍数串, 考虑优化. 我们发现其实可以用一种类似于前缀的方法, 具体来说, 设 \(pre_i \gets pre_{i + 1} + 10^{n - i} \times S_i\), 那么区间 \([j, i]\) 为倍数串 \(\iff (pre_j - pre_{i + 1}) \div 10^{n - i} \equiv 0 \mod D\) .

观察 Subtask, 思考 \(\gcd(D, 10) = 1\) 时怎么做.

显然, 由于 \(D\) 中不含因子 \(2, 5\), 所以上式等价于 \(pre_j \equiv pre_{i + 1} \mod D\), 开个桶记录下桶中前缀和即可.

再来考虑正解, 不妨设 \(D = 2^a \cdot 5^b \cdot x\), 将上式转化为一般情况:

\[(pre_j - pre_{i + 1}) \div 10^{n - i} \equiv 0 \mod D \to \frac{pre_j - pre_{i + 1}}{2^{n - i} \cdot 5^{n - i}} \equiv 0 \mod 2^a \cdot 5^b \cdot x \\ \Updownarrow \\ \begin{cases} \begin{aligned} & pre_l \equiv pre_{r + 1} \mod x \\ & \frac{pre_j}{2^{n - i} \cdot 5^{n - i}} \equiv \frac{pre_{i + 1}}{2^{n - i} \cdot 5^{n - i}} \mod 2^a \\ & \frac{pre_j}{2^{n - i} \cdot 5^{n - i}} \equiv \frac{pre_{i + 1}}{2^{n - i} \cdot 5^{n - i}} \mod 5^b \end{aligned} \end{cases} \]

转化根据中国剩余定理得到.

容易发现令 \(\omega = \min(a, b) \le 20\), 显然, 对于一个数若其超过 20 位, 一定有:

\[\begin{cases} \begin{aligned} \left(\frac{S_j \times 10^{n - j}}{2^{n - i}5^{n - i}} = S_j \times 10^{i - j} \right)\equiv 0 \mod {2^a} \\ \left(\frac{S_j \times 10^{n - j}}{2^{n - i}5^{n - i}} = S_j \times 10^{i - j} \right)\equiv 0 \mod {5^b} \end{aligned} \end{cases}\\ \]

解释一下, 这里相当于将前缀和拆开来看, 其实就是一个带 \(10\) 的幂次的 \(S_i\) 之和.

时间复杂度 \(\mathcal{O}(n \omega)\), 其中 \(\omega \le 20\).

T3

算法

动态规划, 前缀和.

思路

先思考一个不考虑重复的 DP. 设 \(f_{i, j}\) 表示删除第 \(i\) 行第 \(j\) 个的方案数, 那么就可以由第 \(i - 1\) 行的 \([j - k, j + 1]\) 转移而来:

\[f_{i, j} = \sum_{p = \max(1, j - k)}^{\min(m, j + k)} f_{i - 1, p} \]

但是这样会有重复, 因为在一块内删除每一个都是一样的, 我们需要删掉重复计算的贡献.

\(g_{i, j}\) 表示在第 \(i\)\(j - 1, j\) 重复的部分, 如果 \(s_{i, j - 1} \ne s_{i, j}\) 两种方案显然不会等价, 否则他们相交的贡献区间为 \([j - k, j - 1 + k]\):

\[g_{i, j} = \sum_{p = \max(1, j - k)}^{\min(m, j - 1 + k)} g_{i - 1, p} [s_{i, j} = s_{i, j - 1}] \]

那么第 \(i\) 行删除掉第 \(j\) 个的方案其实是 \(f_{i, j} - g_{i, j}\).

直接转移是 \(\mathcal{O}(n^3)\) 的, 需要用前缀和优化一下.

复制代码
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
#include "iostream" using namespace std; constexpr int N = 3e3 + 10, mod = 1e9 + 7; #define int long long int n, m, k; char s[N][N]; void init() { cin >> n >> m >> k; for (int i = 1; i <= n; ++i) cin >> (s[i] + 1), s[i][0] = '2'; } int f[N][N], g[N][N]; void calculate() { for (int j = 1; j <= m; ++j) f[1][j] = 1, g[1][j] = (s[1][j] == s[1][j - 1]); for (int i = 2; i <= n; ++i) { for (int j = 1; j <= m; ++j) f[i - 1][j] = (f[i - 1][j] + f[i - 1][j - 1]) % mod, g[i - 1][j] = (g[i - 1][j] + g[i - 1][j - 1]) % mod; for (int j = 1, l, r; j <= m; ++j) { l = max(1ll, j - k), r = min(m, j + k); f[i][j] = (f[i - 1][r] - f[i - 1][l - 1] + mod) % mod; f[i][j] = (f[i][j] - (g[i - 1][r] - g[i - 1][l]) + mod) % mod; if (s[i][j] != s[i][j - 1]) { g[i][j] = 0; continue; } r = min(m, j - 1 + k); g[i][j] = (f[i - 1][r] - f[i - 1][l - 1] + mod) % mod; g[i][j] = (g[i][j] - (g[i - 1][r] - g[i - 1][l]) + mod) % mod; } } int ans = f[n][1]; for (int j = 2; j <= m; ++j) ans = (ans + f[n][j] - g[n][j] + mod) % mod; cout << ans << '\n'; } void solve() { init(); calculate(); } signed main() { cin.tie(nullptr)->sync_with_stdio(false); solve(); return 0; }
posted @   Steven1013  阅读(4)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
展开