2025.1.12 CW 模拟赛

题面 & 题解

T1

思路

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

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

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

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

#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)\) 的, 需要用前缀和优化一下.

#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 @ 2025-01-12 20:25  Steven1013  阅读(12)  评论(0)    收藏  举报