9.22~9.28

若是生命,已无法迎来明天,恍若吹熄的灯火。待我阖眼,待我阖眼。

不能再摆下去了!

比赛总结

校内模拟赛 9.23:\(100 + 100 + 30 + 70 \to 100 + 30 + 0 + 70\)

校内模拟赛 9.28:\(100 + 100 + 10 + 100 \to 0 + 20 + 10 + 100\)

事实证明我有打很高分的能力,比赛的时候也确实很多时候是在浪费时间做想不出来的题,为什么不对拍呢?

CF1053Div2:犯病,读错三道题,并且使用巨大复杂做法写 D 还没写出来。

ABC425:犯病,不分析复杂度导致的。E, F 复杂度都大于标算,虽然 E 卡了很久常数过了,但 F 还是没过。其实是没有利用题目性质,使用了莫名其妙的状态,导致时间复杂度烂了。

LGR-242:打的还行,前期快速得了很多分,但是后期做 T3 不知道怎么判断左右括号卡了很久只会 65 就有点红温了,导致写 T4 的时候脑子不知道在干什么使用记搜写数位 DP 然后烂完了。从前期 rk < 10,到最后 rk 100 名左右。

还是注意考场心态问题,做不出来的题不要急,OI 赛制就先跳过,别的赛制就先看看有没有什么没利用上或没发现的性质条件,或是重读题目。

ARC204A

问题相当于先将 \(A, B\) 归并,并且要求任意前缀中 \(B\) 的数量小于等于 \(A\) 的数量。

然后我就不会了,其实是个套路。

发现这个东西很类似括号序列,所以不妨考虑一个折线状物。对于一个 \(A_i\),向右 \(A_i\),向下 \(A_i\);对于一个 \(B_i\),向右 \(B_i\),向上 \(B_i\)。但由于 \(\max\) 的原因,所有第二象限的东西都要向上平移,而这个平移的距离与最低点有关。

\(S = \sum_{i = 1}^n B_i - A_i\),则我们最后应该到达 \((S, S + k)\) 的位置,其中 \(k\) 是我们总共平移的距离。题目限制 \(S + k \in[L, R]\),我们不妨差分一下,求 \(k \leq R - S\)\(k \leq L - 1 - S\)

然后想想 \(k\) 是什么,容易发现 \(k\) 就是不平移时的最低点的绝对值,即前缀 \(\sum A - \sum B\)\(\max\)

然后我们就可以 DP 计数满足任意前缀中 \(A\) 的个数不少于 \(B\) 的个数且任意前缀中 \(\sum A - \sum B \leq x\)。其中 \(x \in \{L - 1 - S, R - S\}\)

\(f_{i, j}\) 表示 \(A\) 到第 \(i\) 位,\(A_i\) 之前有 \(j\)\(B\) 的方案数。设 \(p\) 为使 \(\sum_{k = 1}^{i - 1} A_k - \sum_{k = 1}^p B_k \leq x\) 的最小 \(p\),转移的时候有 \(\sum_{k = p}^{\min(j, i - 2)} f_{i - 1, k} \to f_{i, j}\)\(p\) 可以直接维护,转移前缀和优化一下。复杂度 \(O(n^2)\)

#include <bits/stdc++.h>
using namespace std;
#define rep(i, a, b) for (int i = (a); i <= (b); i ++)
#define fro(i, a, b) for (int i = (a); i >= b; i --)
#define INF 0x3f3f3f3f
#define eps 1e-6
#define lowbit(x) (x & (-x))
#define reg register
#define IL inline
typedef long long LL;
typedef std::pair<int, int> PII;
inline int read() {
	int x = 0, f = 1;
	char ch = getchar();
	while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); }
	while (ch >= '0' && ch <= '9') { x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar(); }
	return x * f;
}

const int N = 5010, Mod = 998244353;
int n, L, R;
int a[N], b[N], sa[N], sb[N], f[N][N], g[N][N];

int solve(int x) {
	if (x < 0) return 0;
	for (int i = 1; i <= n; i ++)
		for (int j = 1; j <= n; j ++) f[i][j] = 0;
	f[1][0] = 1;
	for (int i = 0; i <= n; i ++) g[1][i] = 1; 
	int p = 0;
	for (int i = 2; i <= n; i ++) {
		while (p <= n && sa[i - 1] - sb[p] > x) p ++;
		for (int j = 0; j < i; j ++) 
			if (sa[i] - sb[j] <= x && p <= min(j, i - 2)) 
				f[i][j] = ((g[i - 1][min(j, i - 2)] - g[i - 1][p - 1]) % Mod + Mod) % Mod;
		g[i][0] = f[i][0];
		for (int j = 1; j <= n; j ++) g[i][j] = (g[i][j - 1] + f[i][j]) % Mod;
	}
	return g[n][n];
}

int main() {
	cin >> n >> L >> R;
	int S = 0;
	for (int i = 1; i <= n; i ++) cin >> a[i], S -= a[i], sa[i] = sa[i - 1] + a[i];
	for (int i = 1; i <= n; i ++) cin >> b[i], S += b[i], sb[i] = sb[i - 1] + b[i];
	int ans = (solve(R - S) - solve(L - 1 - S)) % Mod;
	cout << (ans + Mod) % Mod << endl;
	return 0;
}

ARC204B

操作次数最小是容易的。只需要连排列环,次数为 \(n - r\),其中 \(r\) 为环数。考虑对于每个环分析最大得分。

环很难做,考虑破环成链。注意到此时无论删哪条边都不影响贡献,因为一对点的贡献如果跨越了删掉的边,则一定能将这对点在另一侧内部的点全部做完后再做这两个点。

此时很高妙的可以直接使用区间 DP,令 \(f_{i, j}\) 为从 \(i \to \cdots \to j\) 的这条链上删点,最后剩下 \(i\) 的最大答案。此时有转移 \(f_{i, mid - 1} + f_{mid, j} + [a_i \equiv a_{mid} \pmod n] \to f_{i, j}\)

但这样做复杂度为 \(O(n^3k^3)\) 太慢了,考虑很厉害的优化。

你会发现直接这样枚举 \(mid\) 太蠢了,对于 \(a_i\) 最多只有 \(k\) 个点会和它产生贡献。所以我们不妨将转移分成 \(a_i\) 产生贡献与不产生贡献两种。

产生贡献时的转移只需要提前记录一下同余的即可。不产生贡献时,有 \(f_{i, j} \leftarrow \max(f_{i + 1, j}, f_{i, j - 1})\)

可能还是区间 DP 题做的太少了,根本想不到区间 DP 和这种优化。

#include <bits/stdc++.h>
using namespace std;
#define rep(i, a, b) for (int i = (a); i <= (b); i ++)
#define fro(i, a, b) for (int i = (a); i >= b; i --)
#define INF 0x3f3f3f3f
#define eps 1e-6
#define lowbit(x) (x & (-x))
#define reg register
#define IL inline
typedef long long LL;
typedef std::pair<int, int> PII;
inline int read() {
	int x = 0, f = 1;
	char ch = getchar();
	while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); }
	while (ch >= '0' && ch <= '9') { x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar(); }
	return x * f;
}

const int N = 5010;
int n, k, p[N], vis[N], f[N][N], id[N];
vector<int> divs[N];

int main() {
	cin >> n >> k;
	for (int i = 1; i <= n * k; i ++) cin >> p[i];
	int ans = 0;
	for (int i = 1; i <= n * k; i ++) if (!vis[i]) {
		int len = 0, u = i; 
		while (!vis[u]) id[++ len] = u, vis[u] = 1, u = p[u]; 
		for (int i = 0; i < n; i ++) divs[i].clear();
		for (int i = 1; i <= len; i ++) divs[id[i] % n].push_back(i);
		for (int i = 1; i <= len; i ++) {
			for (int j = 1; j <= len; j ++) f[i][j] = -INF;
			f[i][i] = 0;
		}
		for (int l = 2; l <= len; l ++) 
			for (int i = 1; i + l - 1 <= len; i ++) {
				int j = i + l - 1;
				f[i][j] = max(f[i + 1][j], f[i][j - 1]);
				for (auto k : divs[id[i] % n]) 
					if (k > i && k <= j) f[i][j] = max(f[i][j], f[i][k - 1] + f[k][j] + 1);
			}
		ans += f[1][len]; 
	}
	cout << ans << endl;
	return 0;
}

ARC204C

ARC 怎么都是神仙题。

题目相当于给出了一堆环,直接考虑很困难,考虑一下答案是什么。会发现 \(01\) 能贡献 \(2\)\(02/00\) 能贡献 \(1\)\(12/11/22\) 没有贡献(反着一样的道理)。

会发现对于一个 \(0\) 对于左边和右边至少存在两点贡献,而 \(01\) 会多一点贡献,而 \(00\) 由于会算重相当于要减一点贡献。设 \(01\)\(X\) 个,\(00\)\(Y\) 个,则 \(ans = 2A_0 + X - Y\)。我们需要最大化 \(X-Y\)

考虑贪心填数,不妨先填 \(2\),再填 \(0\),最后填 \(1\)。假设已经填好了 \(2\)\(0\),则此时一个位置填 \(1\) 会增加一点贡献或两点贡献。设填完 \(0\) 后填 \(1\) 可能出现这两种贡献的位置的个数分别为 \(X_1, X_2\)。与此时 \(Y\) 的个数组成三元组 \((X_1, X_2, Y)\)

考虑铺上 \(2\) 后贪心填 \(0\),先填偶环,隔一位填一个;然后填奇环,一样的操作填 \(\frac{len - 1}{2}\) 个,注意先填环长的;然后还能填的话对于奇环中相邻的两个空格填一个(原因是 0220 填成 0200 \(X_2 \leftarrow X_2 + 1, X_1 \leftarrow X_1 - 2, Y \leftarrow Y + 1\));还不行的话填单点(\(Y \leftarrow Y+2\));还不行就随便填。

然后就可以大力分讨一下计算答案。设 \(K\) 为单点个数,\(S\) 为环长。

  1. \(A_0 \leq \frac{\sum S_{even}}{2}\)。则三元组为 \((0, A_0, 0)\),或 \((2, A_0 - 1, 0)\)。当不存在 \(\frac{\sum T}{2} = A_0\) 时是后一种情况。
  2. \(\frac{\sum S_{even}}{2} < A_0 \leq \frac{\sum S_{even} + \sum S_{odd} - |S_{odd}|}{2}\)。这种情况下考虑选出一个 \(S_{odd}\) 的长度为 \(k\) 的子序列满足所有环长覆盖 \(A_0\)。即找到 \(A_0 - \frac{S_{even}}{2} \leq \frac{\sum_{i = 1}^k S_{odd_i} - k}{2}\) 的最小 \(k\),则此时三元组为 \((2k, A_0 - k, 0)\)。这也解释了前面填奇环时从大到小的原因。
  3. \(\frac{\sum S_{even} + \sum S_{odd} - |S_{odd}|}{2} < A_0 \leq \frac{\sum S_{even} + \sum S_{odd} + |S_{odd}|}{2}\)。此时计算出 \(s = A_0 - \frac{\sum S_{even} + \sum S_{odd} - |S_{odd}|}{2}\) 为需要额外填的奇环位置,则三元组为 \((2|S_{odd}| - 2s, A_0 - |S_{odd}|, s)\)
  4. \(\frac{\sum S_{even} + \sum S_{odd} + |S_{odd}|}{2} < A_0 \leq \frac{\sum S_{even} + \sum S_{odd} + |S_{odd}|}{2} + K\)。此时三元组为 \((0, \frac{\sum S_{even} + \sum S_{odd} - |S_{odd}|}{2} , A_0 - \frac{\sum S_{even} + \sum S_{odd} - |S_{odd}|}{2} )\)
  5. \(A_ 0 > \frac{\sum S_{even} + \sum S_{odd} + |S_{odd}|}{2} + K\)。此时三元组的 \(X_2 = \frac{\sum S_{even} + \sum S_{odd} - |S_{odd}|}{2} - [A_0 - (\frac{\sum S_{even} + \sum S_{odd} + |S_{odd}|}{2} + K)] = N - A_0\),此时三元组为 \((0, N - A_0, N - 2(N - A_0))\)

后四种我们都会做,考虑第一种怎么判断。考虑背包,当成多重背包做然后二进制优化一下可做到 \(O(n \sum \log s_v)\)。但显然会被卡成 \(O(n^2)\),进一步会发现由于序列中所有 \(S_{even}\) 的和为 \(n\),所以只有 \(\sqrt n\) 种可能得环长。于是考虑一个很神秘的做法,对于一个价值 \(v\),如果 \(s_v > 2\) 则将 \(2\)\(s_v\) 转化成 \(1\)\(s_{2v}\)

考虑这样复杂度为什么是对的,对于前 \(v \in [1, B)\),最多进行 \(2B\) 次操作,对于 \(S \geq B\),最多存在 \(\frac{n}{B}\) 个数。于是 \(B\)\(\sqrt n\) 就全对了。

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define rep(i, a, b) for (int i = (a); i <= (b); i ++)
#define fro(i, a, b) for (int i = (a); i >= b; i --)
#define INF 0x3f3f3f3f
#define eps 1e-6
#define lowbit(x) (x & (-x))
#define reg register
#define IL inline
typedef long long LL;
typedef std::pair<int, int> PII;
inline int read() {
	int x = 0, f = 1;
	char ch = getchar();
	while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); }
	while (ch >= '0' && ch <= '9') { x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar(); }
	return x * f;
}

const int N = 600010, M = 550;
int n, B, q, p[N];
int f[N], vis[N], K, s[N], S[N];
vector<int> odd, even;

signed main() {
	ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
	cin >> n; B = sqrt(n);
	for (int i = 1; i <= n; i ++) cin >> p[i];
	int sodd = 0, seven = 0, szodd = 0;
	for (int i = 1; i <= n; i ++) {
		if (vis[i]) continue;
		int x = i, len = 0; 
		while (!vis[x]) vis[x] = 1, x = p[x], len ++;
		if (len == 1) K ++;
		else if (len & 1) odd.push_back(len), szodd ++, sodd += len;
		else even.push_back(len), seven += len;
	}
	f[0] = 1; int m = 0;
	for (auto x : even) s[x / 2] ++, m += x / 2;
	for (int i = 1; i <= m; i ++) {
		while (s[i] > 2) s[i * 2] ++, s[i] -= 2;
		while (s[i] --) 
			for (int j = m; j >= i; j --) f[j] |= f[j - i]; 
	}
	sort(odd.begin(), odd.end(), greater<int>()); 
	for (int i = 1; i <= odd.size(); i ++) S[i] = S[i - 1] + odd[i - 1];
	for (int i = 1; i <= odd.size(); i ++) S[i] = (S[i] - i) / 2;
	cin >> q;
	while (q --) {
		int a0, a1, a2;
		cin >> a0 >> a1 >> a2;
		int x, y, z;
		if (a0 <= seven / 2) {
			if (f[a0]) x = 0, y = a0, z = 0;
			else x = 2, y = a0 - 1, z = 0;
		} else if (a0 <= seven / 2 + (sodd - szodd) / 2) {
			int k = lower_bound(S + 1, S + 1 + odd.size(), a0 - seven / 2) - S;
			x = 2 * k, y = a0 - k, z = 0;
		} else if (a0 <= seven / 2 + (sodd + szodd) / 2) {
			int s = a0 - seven / 2 - (sodd - szodd) / 2;
			x = 2 * (szodd - s), y = a0 - szodd, z = s;
		} else if (a0 <= seven / 2 + (sodd + szodd) / 2 + K) {
			x = 0, y = seven / 2 + (sodd - szodd) / 2, z = a0 - seven / 2 - (sodd - szodd) / 2;
		} else {
			x = 0, y = n - a0, z = n - 2 * (n - a0); 
		}
		int ans = 2 * a0 - z;
		int tmp = min(a1, y); ans += 2 * tmp; a1 -= tmp;
		tmp = min(a1, x); ans += tmp; a1 -= tmp;
		cout << ans << endl;
	}
	return 0;
}

根号背包

经典套路根号分治背包。

具体来说,对于小于 \(B\) 的数直接暴力背包转移。对于大于等于 \(B\) 的数,考虑将转移分为两种,一种是将当前序列中的所有数加上 \(1\),一种是往当前序列中增加一个数 \(B\),容易发现所有的操作序列都可以通过这种方式唯一得到。复杂度是 \(O(n\sqrt n)\) 的。

CF2151D

我怎么傻逼到这种题都不会做了。

发现题目给出的限制相当于 L 形。稍微比划一下会发现可以将 L 形变成对于列的性质,对于每个列,必须填一个数。并且每一列只有一些位置能填数,位置类似于 12321。

于是我们把 \(n\) 为奇数的情况中 \(\frac{n + 1}{2}\) 的列先单独处理一下。然后对于剩下的列,相当于这么一个问题:

\(2n\) 个变量 \(x_i\),其中 \(x_i\)\(x_{n + i}\) 均只能填 \(1 \sim i\) 之间的数,且对于每个数 \(i\) 要有 \(a_i\)\(x_j = i\)

容易发现我们从最大的数开始填,组合计数一下就做完了。

void solve() {
	cin >> n; LL S = 0; bool flag = true;
	for (int i = 1; i <= n; i ++) { 
		cin >> a[i]; S += a[i];
		if (i > (n + 1) / 2 && a[i]) flag = false;
	}
	if (!flag || S != n) return cout << 0 << endl, void();
	if ((n & 1) && a[(n + 1) / 2] > 1) return cout << 0 << endl, void();
	int ans = 1; S = -((n & 1) && (!a[(n + 1) / 2])); 	
	for (int i = n / 2; i >= 1; i --) {
		int T = 2 * (n / 2 - i + 1);
		ans = ans * C(T - S, a[i]) % Mod; S += a[i];
	}
	cout << ans << endl;
}

两道模拟赛推式子

似乎都是神秘比赛的原题

对于一个数 \(x\),设它二进制表示下最低位的 \(1\) 是第 \(i\) 位,则 \(\text {lowbit} (x) = 2^i\)

例如 \(\text{lowbit}(5)= 1, \text{lowbit}(12) = 4\)

定义对 \(x\) 的一次变换为:有 \(50\%\) 的概率变成 \(x + \text{lowbit}(x)\) ,有 \(50\%\) 的概率变成 \(x - \text{lowbit}(x)\)

定义 \(f(x)\) 为对 \(x\) 不停变换,变换到 \(0\) 的期望变换次数。

给定 \(L, R\),求 \(\sum_{x=L}^Rf(x)\)

\(0 \leq L \leq R \leq 2^{31} - 1\)

好像是某个神秘平台的原题。

会发现做到一定程度会出现一个 \(100\) 加上 \(\operatorname{lowbit}\) 后变成 \(1000\) 的情况。于是考虑这种情况下相当于求一个无穷级数,容易发现这样下去期望需要两次操作。朴素做法是直接记忆化,能拿 70 分。

考虑推式子,将答案差分,考虑如何求 \(G(n) = \sum_{i = 1}^n f(i)\),分类讨论:

\(n\) 为奇数,令 \(k = \frac{n - 1}{2}\)。则 \(G(n) = \sum_{i = 0}^k f(2i + 1) + \sum_{i = 1}^k f(2i)\)

前面的式子我们会发现 \(f(2i + 1) = \frac{1}{2}[f(2i) + f(2i + 2)] + 1 = \frac{1}{2}[f(i) + f(i + 1)] + 1\)。所以 \(\sum_{i = 0}^k f(2i + 1) = k + 1 + \frac{f(k + 1)}{2} + \sum_{i = 1}^k f(i)\)

后面的式子就是 \(\sum_{i = 1}^k f(i)\)。合起来会发现 \(G(n) = 2G(k) + k + 1 + \frac{f(k + 1)}{2}\)。此时对于 \(f(k + 1)\) 可以直接记忆化计算,容易发现此时计算的个数不超过 \(\log n\),计算单个的复杂度不超过 \(\log n\),总和 \(O(\log^2n)\)

\(n\) 为偶数的情况没有本质区别,有 \(G(n) = 2G(k) + k - \frac{f(k)}{2}\)


给定 \(n\),输出 \(\sum_{i=1}^{n-1} i⊕(n-i)\)。这里 \(⊕\) 表示异或。

\(2 ≤ n ≤ 10^{500}\)

同样是分奇偶推式子,对于 \(n = 2k + 1\) 的情况,令 \(f(n) = \sum_{i = 1}^{n - 1} i \oplus (n - i)\)

\(f(n) = \sum_{i = 1}^{2k} 2i \oplus (2k + 1 - 2i) = \sum_{i = 1}^{2k} 2i \oplus (2k - 2i) + 2k = 4 \sum_{i = 1}^{k} i \oplus (k - i) + 2k = 4f(k - 1) + 6k\)

\(n = 2k\) 的情况类似的可以得到 \(f(n) = 2f(k) + 2f(k - 1) + 4(k - 1)\)


总而言之,对于二进制推式子题考虑分奇偶性讨论,然后根据题目性质将大的东西转化成小的能递归算的东西。

并没有参加 MX 比赛,这是一篇补题笔记。

MX-X21 T3

神人数据,一个显然假的贪心是从前往后能放就放,最后尝试将前后两端合并起来。

然后你会发现将近 50 个测试点还全是多测的情况下,我们仅仅 WA 了最后一个测试点。于是我们考虑将序列翻转做一遍,或者随机化选几个开头多做几遍我们就过了!

正解显然应该每次找最小值向两边拓展,但这样做显然比较困难,可能需要线段树带 \(\log\),但由于跑不满(没卡)所以能够在 \(n = 3e7\) 的时候通过!

怎么优化到 \(O(n)\) 呢,我们考虑只需要向左拓展到最远,那么拓展到的点一定是某一段的开头。而向左拓展的过程可以通过单调栈维护最大值最小值来达成。我们终于用正解通过了这个题!

MX-X21 T4

这个题数据很诡异。我只是漏了一个特判就被活活打断了双腿获得了 \(0\) 分。

首先如果全 \(1\) 或者全 \(0\) 是好判断的。

然后这种博弈题不妨找找必胜态或者必败态,玩一玩首先发现一个时刻连续 \(0\) 块和 \(1\) 块个数相同。接着发现当出现 \(10101010\) 时先手就输了。因为后手可以跟着先手拿的拿,这样最后一定出现 \(101\) 或者 \(010\) 的情景,这时候 \(1\) 或者 \(0\) 玩家连拿两个就赢了。

进一步分析我们发现,如果一个人拿完后同种颜色的块只剩一个则对手就赢了,所以每个人都要尽量不先拿完。所以每个人每次都只会取走一个,并且尽量不把一个块取完。

所以每个块对一个人的贡献是块长减一,由于两种块的数量相同,所以我们只需要判断 \(0\)\(1\) 的数量即可得到胜者。

当然我们需要特判一下,如果总共就只有两个块,那么先手直接拿完了,不需要判断了。

MX-X21 T5

其实这个计数真的很汤吧。但这场单调栈怎么出现频率这么高。

第一档分好做,对于第二档分我们手玩一下会发现对于一个序列中的点 \(a_i\),对于满足条件的 \((i, j)\)\(j\) 是满足单调性可以二分的。于是有 \(O(n^2\log n)\) 的做法。

到这里直接做就行不通了,考虑如何判定一个段的合法性(这里令第一位为 \(1\))。发现段中每次删除的 \(0, 1\) 数量都相同,于是我们需要任意前缀中 \(1\) 的数量大于等于 \(0\) 的数量。于是将 \(0\) 看作 \(-1\),求出前缀和。则 \((i, j)\) 合法当且仅当 \(\forall k \in [i, j] s_k - s_{i - 1} \geq 0\)。二分加上数据结构维护 \([i, j]\) 中的最小值即可判定。复杂度 \(O(n\log^2n)\)

但这么做其实比较蠢,我们从 \(n \to 1\) 扫然后用单调栈或者单调队列维护一下即可。

ABC425E

怎么还卡常。

注意到求的是多重集排列数,由于模数随机,所以维护一下质因子即可。

然而我们注意到组合数很小,所以可以直接预处理计算就可以在 \(O(n)\) 的复杂度内解决!!!

ABC425F

我是傻逼。

我赛时写了一个 \(O(n^22^n)\) 的 DP,并且转移需要使用神秘的 umap 进行去重就全 T 完了。

但其实我们会发现 \(S_1 < S_2 < ... < S_n\),那么有一个很好用的技巧,我们可以使用类似 SOSDP 的方式转移。去重还是一样的去重,对于两个相邻的相同字符只算一种即可。

P14083

这个题很牛啊。

会发现 \(f(p)\) 其实就是 \(p\) 的层数,于是 \(p = 1\) 的时候显然好做,直接二分每次查询 \((2, mid)\) 即可。

对于 \(p\) 任意的情况,显然要先求出 \(p\) 是左括号还是右括号。会发现这直接做非常困难,并且我们显然最多用一次操作。于是不妨找找结论,会发现对于一个左括号左边一定是一车匹配好的括号和没匹配的括号。如果 \(p\) 是左括号,那么匹配好的括号显然不会影响奇偶性,而没匹配的括号一定有 \(f(p)\) 个。于是我们得到当 \(p \equiv f(p) \pmod 2\) 时是左括号,反之是右括号。

然后就好做了,直接二分/倍增即可。然而直接二分/倍增可能会多出一次操作,显然开头的操作不能优化,考虑优化结尾的一次操作。明显如果最后剩下一个长度为 \(2\) 的区间,只有一个会是答案,因为左括号和右括号下标显然奇偶性不相同。

然后这个题数据非常弱啊,怎么不卡最后一步不判断奇偶性直接倍增 \(17\) 次。

P14084

感觉是基本功题目,但显然我基本功不太行。

状态的设计要有一点技巧,乱做复杂度全错完了。

\(f_{i, j}\) 为前 \(i\) 段比 \(L^i\) 小,第 \(i\) 段填了 \(j\)\(1\) 的方案数,则转移分为两种,若前面都取到了上限 \(L\) 则第 \(i\) 段需要单独计数;若前面有至少一段没取到上限,则这一段随便取,可以组合计数。
[
f_{i, j} \leftarrow h_j + \binom{n}{j} \sum_{k = 0}^{\min(n, m - j)} f_{i - 1, k}
]
\(h_j\) 即为对于单独一段存在 \(j\)\(1\) 并且小于 \(L\) 的段数。计数考虑与上文类似的有 \(g_{i, j}\) 表示前 \(i\) 位比 \(L\) 的前 \(i\) 位小,已经填了 \(j\)\(1\) 的方案数。转移有 \(g_{i, j} \leftarrow g_{i - 1, j} + g_{i - 1, j - 1}\),并且设 \(L\) 的前 \(i\) 位有 \(k\)\(0\),若 \(L_i = 1\),则 \(g_{i, k} \leftarrow g_{i, k} + 1\)

前缀和优化一下容易做到 \(O(nk)\),记得判断能否全部取到上限,可以的话答案还要加一。

然后考虑优化,求 \(h\) 的复杂度为 \(O(n ^ 2)\) 可以通过,对于求 \(f\) 的过程考虑将其放到矩阵上。

配矩阵系数时一般先确定 \(A\) 的行列数,显然本题为 \((n + 2) \times (n + 2)\),因为还要表示 \(h\) 的贡献,然后根据转移式子配即可。

const int N = 210, Mod = 998244353;
int n, m, g[N][N], f[N], C[N][N];
LL k;
string s;

inline int add(int a, int b) { return a + b >= Mod ? a + b - Mod : a + b; }

struct Matrix {
    int a[N][N];
    int *operator [] (int i) { return a[i]; }
    void init() {
        for (int i = 0; i <= 201; i ++)
            for (int j = 0; j <= 201; j ++) a[i][j] = 0;
    }
    Matrix friend operator * (Matrix a, Matrix b) {
        Matrix c; c.init();
        for (int i = 0; i <= 201; i ++)
            for (int k = 0; k <= 201; k ++)
                if (a[i][k]) 
                    for (int j = 0; j <= 201; j ++)
                        c[i][j] = add(c[i][j], 1ll * a[i][k] * b[k][j] % Mod); 
        return c;   
    }
} res, A;

int main() {
    ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    cin >> s >> k >> m; n = s.size(); s = ' ' + s;
    int cnt = 0;
    for (int i = 1; i <= n; i ++) {
        g[i][0] = g[i - 1][0];
        for (int j = 1; j <= n; j ++) 
            g[i][j] = add(g[i - 1][j], g[i - 1][j - 1]);
        if (s[i] == '1') {
            g[i][cnt] = add(g[i][cnt], 1); 
            cnt ++;
        }
    } 
    for (int i = 0; i <= n; i ++) 
        for (int j = 0; j <= i; j ++) {
            if (!j) C[i][j] = 1;
            else C[i][j] = add(C[i - 1][j - 1], C[i - 1][j]); 
        }
    for (int i = 0; i <= n; i ++) {
        if (i + cnt <= m) f[i] = g[n][i];
        for (int j = 0; j <= min(n, m - i); j ++) 
            f[i] = add(f[i], 1ll * g[n][j] * C[n][i] % Mod);
    }
    k -= 2;
    if (k == 0) {
        int ans = 0;
        for (int i = 0; i <= n; i ++) ans = add(ans, f[i]); 
        cout << add(ans, 2 * cnt <= m) << endl;
        return 0;
    }
    for (int i = 0; i <= n; i ++) res[0][i] = f[i]; 
    res[0][n + 1] = 1;
    for (int i = 0; i <= n; i ++)
        for (int j = 0; j <= min(n, m - i); j ++) 
            A[j][i] = C[n][i];
    for (int i = 0; i <= n; i ++) 
        if (cnt * 2 <= m && cnt + i <= m) A[n + 1][i] = g[n][i]; 
    A[n + 1][n + 1] = 1;
    while (k) {
        if (k & 1) res = res * A;
        k >>= 1; A = A * A;
    }
    int ans = 0;
    for (int i = 0; i <= n; i ++) ans = add(ans, res[0][i]); 
    cout << add(ans, 2 * cnt <= m) << endl;
    return 0;
}

yzoj 6796

先令 \(c_i = (b_i - a_i + 4) \bmod 4\),则题目转化为对某些 \(c_i\) 加上 \(4\) 的倍数,要求 \(\sum_{i = 1}^n \max(c_i - c_{i + 1}, 0)\) 最小,这是经典贪心。

然后考虑差分一下,记 \(d_i = c_i - c_{i + 1}\),一个区间加就是 \(d_l - 4, d_r + 4\)。考虑一次加减的贡献为:
[
\Delta = \max(d_l - 4, 0) - \max(d_l, 0) + \max(d_r + 4, 0) - \max(d_r, 0)
]
分类讨论后会发现只有 \(l = 3, r = -3\)\(l = 2, r = -3\)\(l = 3, r = -2\) 三种情况下 \(\Delta < 0\)

于是直接做就对了。

注意一定要从后往前扫,因为越往后的东西匹配是越困难的。

posted @ 2025-09-28 22:04  はなこくん  阅读(18)  评论(0)    收藏  举报