A Rock N Roll Fantasy

ARC187

  • 直接做没有思路?考虑换一个角度!不要被已经的思路局限!
  • 全部类型的计数题拆一下贡献!
  • 对于单次冒泡排序 \([1, i]\) 的前缀会在遍历到 \((i+1)\) 时确定。遍历到 \(i\)\(i\) 位置上一定是前缀最大值,这也是一种刻画方法!
  • 对于最小化极差类问题,考虑直接枚举某一个值然后另一个值枚举,省选联考卡牌也是如此。

A

死因:看到非递减序列,看到神秘交换,直接想到使用差分数组刻画,但是这样太难了,做不了,然后我就不会看 T2 去了(因为看上去更容易刻画)实际上是两个都搞炸了,破防离场。所以说如果这个行不通就应该及时换思路而不是对着它死磕。

进行一定观察,发现如果连续进行两次 \(i\) 操作会产生 \(A_i + k, A_{i+1}+k\) 的效果。这个没什么动机,就是单纯的观察,而且也不难,可能可以手玩得到。

然后我们就可以构造 \(1\sim (n - 1)\) 合法。考虑 \(A_n\) 的情况。

  • \(A_n \ge A_{n - 1}\) 直接合法。
  • \(A_n < A_{n - 1}, A_n + k \le A_{n - 1}\),此时交换一次后可以不断对最后两个位置同时 \(+k\)
  • \(A_n < A_{n - 1}, A_n + k > A_{n - 1}\),注意到此时给 \(A_{n - 1},A_{n - 2}\) 同时 \(+k\) 就变成第二种情况了。

B

最严重的问题出在观察到每个联通块一定是一个区间后没有想到按照 \((i, i+1)\) 这个缝隙来刻画和计算不在一个联通块的充要条件,这样的话实际上我们只要分析两个块即可。我一开始分析的充要条件是针对一个序列的整体计算的,导致前面到后面影响很大,不利于直接计数。

如果从一个二维平面的角度考虑,那么一个点能到的连边就是它左下角的所有点,此时不难想到一个联通块都在一个区间内的性质。现在考虑为什么 \([l, p], [p+1, r]\) 会到两个联通块去。这等价于 \(\forall i \in[l, p], a_i > \max\limits_{x\in[p+1, r]}a_x\),也就是 \(\min\limits_{x\in [l, p]}a_x > \max\limits_{x\in[p+1, r]}a_x\),然后直接 dp 即可。

这种拆贡献没有想到,必须严肃拷打。

C

这道题就更搞笑了,做了一万遍冒泡排序,但是仍然没弄清它怎么刻画。对于这道题实际上是直接刻画的,到了 \(i\) 这个位置上实际上是前缀最大值,同时可以确定 \((i - 1)\) 取值。显然的,用 \((i, j, S)\) 代表扫描到第 \(i\) 个位置,前缀最大值为 \(j\),目前用了 \(S\) 内的数,可以刻画好一个盘面,直接转移即可。然后发现 \(S\) 太大了,但是注意到我们只关心 \(S\) 中小于 \(j\) 有多少空位,和大于 \(j\) 有多少空位,大于 \(j\) 全是空位,小于 \(j\) 只有 \(i\) 个位置有值,所以可以将这些 \(S\) 直接合并起来。

所以设 \(f[i, j]\) 为前 \(i\) 个数,前缀最大值为 \(j\) 的方案数,那么

  • \(Q_{i} = -1\)

    • \(k > j\) 那么转移到 \(f[i + 1, k]\)
    • \(k = j\),那么要搭配 \((j - i - r)f[i, j]\) 这个系数,其中 \(r\)\(Q_{i+1}\sim Q_n\) 中不为 \(-1\) 且小于 \(j\) 的数量。注意若 \(Q_{i+1}< j\),此时 \((i+1)\) 位置无法填 \(Q_{i+1}\)
    • \(k\ge j\),同时 \(k\) 不等于除了 \(Q_{i+1}\) 以外所有出现过的 \(Q\)
  • \(Q_{i} \not= -1\)

    • \(j = Q_{i}\),那么 \((n - j - r)f[i, j]\) 转移到 \(f[i + 1, k]\)\(k\) 条件同上,\(r\) 同上。
    • \(j \not= Q_i\),那么 \(f[i, j]\) 转移到 \(f[i+1, j]\)

上面的有点问题,后来又推了,不管了我咕咕咕


ARC156

C

对于这种构造题,要记住 \(N\le 5000\) 不一定是这道题是个 \(O(N^2)\) 的树上 dp,也有可能是出题人的 checker 写不到太优秀,虽然这听起来挺搞笑的,但是我一开始的确是这样想的。

LCS 没有什么好的手法来刻画,那么我们应该从局部开始考虑。链和菊花图。链显然是倒序最后 LCS 为 \(1\),因为两个序列一定会出现重复元素,所以 LCS 不可能更小。对于菊花图也同理。我们不难发现答案的下限实际上是 \(1\),因为对于 \(x\) 我们总是可以找到 \(P_y = x\)\(y\) 联系起 \(x, y\) 使得答案至少为 \(1\)。那么到了这里是不是可以大胆猜测可以构造答案为 \(1\) 的方案了呢?

考察 LCS 为 \(1\) 的序列的充要条件,只保留再两个序列中同时出现过的数,那么应该是严格的倒序。同时考虑在树上的一些内容,注意到树上只有两个端点都是叶子的路径才可能产生贡献。结合菊花图和链的情况,可以想到可以每次删除两个叶子 \(u, v\),接着使 \(p_v = u, p_u = v\)。如果最后删除到它们的 LCA 处只剩下一个孤点,那么此时是完全倒序,否则完全删完的话 LCA 的值就不在之中了,那么也是倒序的。这样的构造是合法的。

总结一下,这道题应该首先从特殊情况出发(链,菊花图),然后考虑答案的可能下界。对于构造题,这些下界往往就是可以构造出来的。然后再考虑构造方法。考虑构造的时候,及时排除不优的可能,以此简化结构。

D

这也太牛了!

注意到这样 xor 可以消除很多重复的和,这启发我们直接统计每个和出现次数,也就是:

\[\operatorname{xor}_{\sum c_i = k}(\binom{k}{c_1, c_2, \dots, c_n}\bmod 2)\sum\limits_{i = 1}^n c_i a_i \]

如果你对 P3773 还有印象,一定会想到利用 Lucas 定理拆开上面的组合数挖掘性质。

\[\binom{k}{c_1, c_2, \dots, c_n} = \prod\limits_{i = 1}^n\binom{k - \sum\limits_{j = 1}^{i - 1}c_j}{c_i} \]

根据 Lucas 定理可以得到 \(\binom{m}{n} \bmod 2 = 1\iff n\subseteq m\),于是可以得到 \(c_1, c_2, \dots, c_n\)\(k\) 的一种不交划分。到了这里就很容易了,直接拆位然后算贡献。因为 \(+, \operatorname{xor}\) 都是只涉及低位向高位的,设 \(f[i, S]\) 为考虑了 \([0, i)\) 这些位的 xor,向 \(i\) 之后进位为 \(S\)。大力转移即可。

总结:主要在于观察到把式子化成那种形式,也许多手玩手玩就会出来了?

E

首先考虑判定,结论是设 \(S = \sum\limits_{i = 1}^n X_i\),若 \(S\) 为偶数且 \(X_i + X_{i \bmod n + 1} \le \dfrac{S}{2}\) 那么合法。必要性显然,充分性上,若 \(\max(X_i + X_{i \bmod n + 1}) < \dfrac{S}{2}\) 那么显然配对任何一对下一对一定满足,否则,当 \(n = 4\) 时条件显然成立,当 \(n > 4\) 时因为最多只会存在一个 \(i\) 满足 \(X_{i} + X_{i\bmod n + 1} = \dfrac{S}{2}\),所以直接给其中一个和另外任意一个匹配上即可。

现在考虑计数。

很容易注意到满足 \(X_i + X_{i\bmod n + 1} > \dfrac{S}{2}\)\(i\) 最多只有两个,并且一定相邻,于是可以想到钦定一个或者两个位置不合法,然后做容斥。

首先考虑不钦定任何位置的情况,要求 \(S\) 为偶数且 \(S\le K\)。若 \(S = K\),那么就是很经典的容斥问题,钦定哪些位置是超出 \(m\) 的,那么就有:

\[g(n, K) = \sum\limits_{i = 0}^n(-1)^i\binom{n}{i}\binom{K - (m+1)i + n - 1}{n - 1}\\f(n, K) = \sum\limits_{t = 0}^{\frac{K}{2}}g(n, 2t), h(n, K) = \sum\limits_{t = 0}^{K}g(n,t) - f(n, K)\\ N_1 = f(n, K) \]

钦定 \(X_i + X_{i\bmod n + 1} > S/2\),那么剩下的 \((n - 2)\) 个数字只和就应该小于 \(X_i + X_{i\bmod n + 1}\),那么就有方案数为:

\[N_2 = n\sum\limits_{a,b \in [0, m]}([(a+b)\bmod 2 = 0]f(n - 2, \min(k - a - b, a + b - 1)) +[(a+b)\bmod 2 = 1]h(n - 2, \min(k - a - b, a + b - 1))) \]

钦定 \(X_{i - 1}+ X_i > S/2, X_{i} + X_{i\bmod n + 1} > S/2\),记 \(a = X_{i - 1}, b = X_{i}, c = X_{i\bmod n + 1}, T = S - a - b - c\),那么就应该有 \(b - S > |a - c|\),于是:

\[N_3 = n\sum\limits_{a, b, c\in[0, m]} ([(a+b+c)\bmod 2 = 0]f(n - 3, \min(k - a - b - c, b - |a - c| - 1)) + [(a+b+c) \bmod 2 = 1]h(n - 3, \min(k - a - b - c, b - |a - c| - 1))) \]

最终答案为 \((N_1 - N_2 + N_3)\)。现在问题就出在加快运算了。


\(N_2, N_3\) 的计算中,\(f\) 涉及到的第一维只有 \((n - 2), (n - 3)\),第二维的范围是 \(O(m)\) 的,在预处理 \(f\)\(h\) 上容易 \(O(nm)\)。计算 \(N_2\) 时本身就是 \(O(m^2)\) 的,计算 \(N_3\) 时,枚举 \(a, b\),不失一般性,假设 \(c>a\)(对于 \(a = c\) 单独算),分类讨论一下 \(k - a - b - c\)\(b - c + a - 1\) 的大小,然后维护一些前缀和状物即可。

考虑 \(N_1\) 的计算。我是不会告诉你这个地方我想了一年都没想出来 QAQ 呜呜呜怎么这么笨式子重新列出来:

\[\sum\limits_{t = 0}^{\frac{K}{2}}\sum\limits_{i = 0}^n (-1)^i \binom{n}{i}\binom{2t - (m+1)i + n - 1}{n - 1}\\ = \sum\limits_{i = 0}^n(-1)^i \binom{n}{i}\sum\limits_{t = 0}^{\frac{K}{2}}\binom{2t - (m+1)i+n - 1}{n - 1} \]

注意到 \(-(m+1)i+n - 1\) 为常数,预处理前缀和即可。

#include <bits/stdc++.h>
using namespace std;
const int N = 3000 * 3000 + 3000, M = 3000, Mod = 998244353;
int Abs(int x) {
	return ((x < 0) ? (-x) : (x));
}
void upd(int &x, int y) {
	x = ((x + y >= Mod) ? (x + y - Mod) : (x + y));
}
int qpow(int n, int m) {
	int res = 1;
	while(m) {
		if(m & 1) res = 1ll * res * n % Mod;
		n = 1ll * n * n % Mod;
		m >>= 1;
	}
	return res;
}
int fac[N + 3], invf[N + 3];
int C(int n, int m) {
	if(m > n) return 0;
	if(n < 0) return 0;
	return 1ll * fac[n] * invf[n - m] % Mod * invf[m] % Mod;
}

int n1, n2, n3, n, m, qk;
int f[M * 2 + 3], g[M * 2 + 3], h[M * 2 + 3];
void prep(int n, int k) {
	for(int K = 0; K <= k; K++) {
		g[K] = 0;
		for(int i = 0; i <= n; i++) {
			int t1 = ((i % 2 == 0) ? 1 : (Mod - 1));
			int t2 = 1ll * C(n, i) * C(K - i * (m + 1) + n - 1, n - 1) % Mod;
			upd(g[K], 1ll * t1 * t2 % Mod);
		}
	}
	f[0] = g[0], h[0] = 0;
	for(int K = 1; K <= k; K++) {
		f[K] = h[K] = 0;
		upd(f[K], f[K - 1]); upd(h[K], h[K - 1]);
		if(K % 2 == 0) upd(f[K], g[K]);
		else upd(h[K], g[K]);
	}
}

int s[N + 10], w[N + 10], sf[N + 10], sh[N + 10];
int main() {
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);

	fac[0] = 1; for(int i = 1; i <= N; i++) fac[i] = 1ll * fac[i - 1] * i % Mod;
	invf[N] = qpow(fac[N], Mod - 2); for(int i = N - 1; i >= 0; i--) invf[i] = 1ll * invf[i + 1] * (i + 1) % Mod;

	cin >> n >> m >> qk; qk = (qk / 2) * 2;
	prep(n - 2, 2 * m);
	for(int a = 0; a <= m; a++) {
		for(int b = 0; b <= m && b + a <= qk; b++) {
			if(!a && !b) continue ;
			if((a + b) % 2 == 0)
				upd(n2, f[min(qk - a - b, a + b - 1)]);
			else upd(n2, h[min(qk - a - b, a + b - 1)]);
		}
	}
	n2 = 1ll * n * n2 % Mod;

	prep(n - 3, m);
	s[0] = f[0], w[0] = h[0];
	for(int i = 1; i <= m; i++) 
		s[i] = s[i - 1], upd(s[i], ((i % 2 == 1) ? h[i] : f[i])),
		w[i] = w[i - 1], upd(w[i], ((i % 2 == 0) ? h[i] : f[i]));
	for(int a = 0; a <= m; a++) {
		for(int b = 0; a + b <= qk && b <= m; b++) {
			int lc = a + 1, rc = min(m, qk - a - b);
			int lw, rw;
			if(lc > rc) continue;
			if(2 * (a + b) >= qk + 1) lw = max(0, qk - a - b - rc), rw = qk - a - b - lc;
			else lw = max(0, b - rc + a - 1), rw = b - lc + a - 1;

			if(rw < lw) continue;
			if((a + b + lc) % 2 == rw % 2) upd(n3, (s[rw] - ((lw == 0) ? 0 : s[lw - 1]) + Mod) % Mod);
			else upd(n3, (w[rw] - ((lw == 0) ? 0 : w[lw - 1]) + Mod) % Mod);
		}
	}
	n3 = 2ll * n * n3 % Mod;
	for(int a = 0; a <= m; a++) {
		for(int b = 0; b <= m; b++) {
			int w = min(qk - 2 * a - b, b - 1);
			if(w < 0) continue;
			if(b % 2 == 0) upd(n3, 1ll * n * f[w] % Mod);
			else upd(n3, 1ll * n * h[w] % Mod);
		}
	}
	
	sf[0] = 1, sh[0] = 0;
	for(int t = 1;  t <= qk; t++) {
		sf[t] = sf[t - 1], sh[t] = sh[t - 1];
		if(t % 2 == 0) upd(sf[t], C(t + n - 1, n - 1));
		else upd(sh[t], C(t + n - 1, n - 1));
	}

	for(int i = 0; i <= n; i++) {
		int t1 = ((i % 2 == 0) ? 1 : (Mod - 1));
		int t2 = C(n, i);

		int tr = 2 * (qk / 2) - (m + 1) * i;
		if(tr < 0) continue;
		int t3 = ((tr % 2 == 0) ? sf[tr] : sh[tr]);
		upd(n1, 1ll * t1 * t2 % Mod * t3 % Mod);
	}

	// cout << n1 << ' ' << n2 << ' ' << n3 << endl;
	upd(n1, Mod - n2), upd(n1, n3);
	cout << n1 << '\n';
}
总结:
  • 虽然说这样有点吃不到葡萄说葡萄酸之嫌,但是这题确实就是恶臭风格计数题开场对充要条件的转化,还有发掘最多只有两个位置不合法其实还是挺考验手法的。
  • 对于这道题的充要条件,会先从一个必要条件:如果 \(X_i + X_{i - 1} > S - X_i - X_{i - 1}\) 那么一定无解出发,再到这个条件就是充要条件。因为可以注意到这个问题的匹配方式很自由,所以不妨大胆直接从必要条件出发,很可能就是充分的。
  • 观察性质。

ARC157

C

组合意义,平方变成取数,没了。

D

注意到,空行空列完全不产生影响。先考虑不存在任何空行空列的情况。

观察到重要性质,\(2(p-1)(q-1) = S\)。于是可以枚举 \(p, q\)。如果没有空行空列那么每一行的分界和每一列的分界会发现是唯一的,都是 \(2p\) 或者 \(2q\) 倍数,这是一个必要条件。然后再二维前缀和判断是否合法即可。时间复杂度 \(O(d(S)nm)\)

E

有人没看到 no child or exactly two children 哈哈我不说是谁。

Y 不相邻,所以从 Y 入手,不难发现 XY 是 Y 数量与根是否为 Y 有关,所以可以考虑对根是否为 Y 分讨。若根不是 Y,那么 XY 数量就是 Y 的数量,而 YX 的数量也就是 Y 的儿子数量,也就是非叶子 Y 数量乘 \(2\)

于是问题变成了能否选出一个 \(Y\) 的独立集合,使得有 \(a\) 个非叶子,\(b\) 个叶子。之要求出最大独立集即可,设 \(f[u, x, 0/1]\) 代表以 \(u\) 为根子树内选了 \(x\) 个叶子,\(u\) 选或者不选,最大独立集大小。然后直接维护即可。时间复杂度 \(O(n^2)\)

F

根本不会,严肃学习,肃然起敬,该加训了。

首先考虑暴力,显然可以 \(f[i, j, S]\) 代表第一个串匹配到了 \(i\),第二个串匹配到了 \(j\)\((\min(i, j), \max(i, j)]\) 内的交换状态为 \(S\) 的字典序最小最长 LCS。注意到 \(|S| = \max(i, j) - \min(i, j)\),所以我们可以直接砍掉一个维度,设 \(f[i, 0/1, S]\) 代表当前最长扩展到了 \(i\),是第一个串还是第二个串,到这里的状态压缩为 \(S\),这里面 \(S\) 额外在所有位之前保留一个最高位的 \(1\),代表长度。时间复杂度为 \(O(n2^n)\)

怎么优化?

接下来这步有点太神秘了:注意到无论什么情况任意三个位置必然能匹配上两个,也就是说最多有 \(\lfloor\dfrac{n}{3}\rfloor\) 个失配的位置。也就是说当 \(|S| > \lfloor\dfrac{n}{3}\rfloor\) 时一定短的一条边需要开始匹配了,不然一定不优。那么我们就可以把 \(|S|\) 长度规定在 \(\lfloor\dfrac{n}{3}\rfloor\) 以内。时间复杂度 \(O(n2^{\frac{n}{3}})\)

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 50, M = 18;
struct node {
	int len; ll state;
	bool operator < (const node &other) const {
		if(len != other.len) return len < other.len;
		else return state > other.state;
	}
	bool operator == (const node &other) const {
		return (len == other.len && state == other.state);
	}
};
int a[N + 3], b[N + 3], n;
int hbit[(1 << M) + 3];
node f[N + 3][2][(1 << M) + 3];

node expand(node T, int x) {
	node tp = T;
	tp.len++;
	tp.state = 2ll * tp.state + x;
	return tp;
}
void upd(node &x, node y) {
	if(x < y) x = y;
}
int main() {
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);
	cin >> n;
	char ch;
	for(int i = 1; i <= n; i++) {
		cin >> ch;
		a[i] = (ch == 'Y');
	}
	for(int i = 1; i <= n; i++) {
		cin >> ch;
		b[i] = (ch == 'Y');
	}

	node ans = (node){0, 0ll};
	int lim = max(2, n / 3 + 2);
	for(int S = 0; S < (1 << lim); S++)
		for(int i = lim - 1; i >= 0; i--)
			if((S >> i) & 1) {
				hbit[S] = i;
				break;	
			}
	for(int i = 0; i <= n; i++)
	for(int w = 0; w <= 1; w++)
	for(int S = (1 << lim) - 1; S >= 1; S--) {
		node t = f[i][w][S]; int hb = hbit[S], j = i - hb;
		if(j < 0) continue;
		if(i == j) {
			if(a[i + 1] == b[i + 1])
				upd(f[i + 1][w][1], expand(f[i][w][S], a[i + 1])),
				upd(f[i + 1][w ^ 1][1], expand(f[i][w][S], a[i + 1]));
			upd(f[i + 1][w][2], f[i][w][S]);
			upd(f[i + 1][w ^ 1][2], f[i][w][S]);
			upd(f[i + 1][w][3], f[i][w][S]);
			upd(f[i + 1][w ^ 1][3], f[i][w][S]);
		}
		else {
			int aim = ((!w) ? (a[i + 1]) : (b[i + 1]));
			int cj = (((w + S) % 2 == 0) ? (b[j + 1]) : (a[j + 1]));
			upd(f[i][w][S >> 1], f[i][w][S]);

			if(cj == aim)
				upd(f[i + 1][w][(S - (1 << hbit[S])) / 2 + (1 << hbit[S])], expand(f[i][w][S], cj));
			if((S - (1 << hbit[S])) + (1 << (hbit[S] + 1)) < (1 << lim)) 
				upd(f[i + 1][w][(S - (1 << hbit[S])) + (1 << (hbit[S] + 1))], f[i][w][S]);

			if(cj == a[i + 1] + b[i + 1] - aim)
				upd(f[i + 1][w][(S - (1 << hbit[S])) / 2 + (1 << (hbit[S] - 1)) + (1 << (hbit[S]))], expand(f[i][w][S], cj));
			if((S + (1 << (hbit[S] + 1))) < (1 << lim))
				upd(f[i + 1][w][(S + (1 << (hbit[S] + 1)))], f[i][w][S]);
		}
	}

	for(int i = 0; i <= n; i++)
	for(int S = 0; S < (1 << lim); S++)
	for(int w = 0; w <= 1; w++)
		upd(ans, f[i][w][S]);

	for(int i = ans.len - 1; i >= 0; i--) {
		int t = ((ans.state >> (ll)i) & 1ll);
		if(!t) cout << "X";
		else cout << "Y";
	}
	cout << '\n';
}

总结:

  • 这道题为什么我想的那么怪?因为我一直在考虑值域在 \(\{0, 1\}\) 的 LCS 有没有高妙的做法。
  • LCS 似乎没有什么比较简洁的 \(O(\dfrac{nm}{w})\) 以内的做法,除非它是一个排列。
  • 回到这道题,\(O(n2^n)\) 是简单的。考虑优化 \(S\) 这个维度。\(S\) 有什么组合意义吗?\(S\) 是暂时没有匹配的位置。那么最长的 \(S\) 就是:最远的没有匹配的两点的距离。
  • LCS 很长,最远的没有匹配的两点相应的距离就不太长了。
  • 还是很高(神)妙(经)的性质题啊。

ARC158

B

枚举 \(i\),那么变成常数:

\[\dfrac{c+x_j+x_k}{cx_jx_k} = \dfrac{1}{x_jx_k}+\dfrac{1}{c}(\dfrac{1}{x_j} + \dfrac{1}{x_k}) \]

对着后面的 \(x\) 取正最大,次大,最小,次小,负最大,次大,最小,次小即可。不写了。

D

这道题是当时集训的时候敬爱的将军讲的,我当时觉得很震撼,然后做法忘了,今天再做,然后还是忘了

观察一下结构:右侧是一个齐次式,左侧是三个齐次式的乘积,同时总次数比右边大 \(1\)。如果 \(F(x, y, z) = tG(x, y, z)\),那么就有 \(F(x/t, y/t, z/t) = G(x/t, y/t, z/t)\)。直接取几个数可能会出现 \(G(x, y, z)\) 无逆元之类的情况,所以要多随几次。

关键在于:对于齐次式或者齐次式的乘积,若次数差为 \(\delta\),那么 \(F(x, y) = t^{\delta}G(x, y) \iff F(x/t, y/t) = G(x/t, y/t)\)。感觉是可以记下来的结论。

E

如果是两点的话 dp \(f[i, 0/1]\) 然后直接转移,转移是:

\[f[i,0] = \min\{f[i-1, 0], f[i - 1, 1]+a_{i, 1}\} + a_{i, 0}\\ f[i, 1] = \min\{f[i-1, 1], f[i - 1, 0]+a_{i, 0}\} + a_{i, 1}\]

放到计数里通常的手法是:\(f[i, j, k]\) 代表转移到了 \(i\) 之后,也就是起点在 \([1, i]\) 内,当前这个位置状态为 \(j, k\)\(val(X, Y)\) 总和。现在注意到这个东西它转移只和 \(f[i, 0] - f[i, 1]\) 有关,似乎可以把它压进来?然后呢?用什么方法优化?明天仔细研究。

我必须严肃反思我第一下的反应是 dp of dp 这档子事,然后手滑(真的只是我手滑啊呜呜呜 QAQ)点开算法标签看到分治这真得加训了。

这种所有路径的处理很容易想到分治。对于 \([l, r]\) 考虑所有跨过第 \(mid\) 列的,预处理第 \(mid\) 到第 \(l\) 列这样的一串后缀的最小距离 \(f[i, s, t]\)。其中 \(s\) 为起点所在的行,\(t\) 为结尾所在的行。同理预处理 \((mid+1)\)\(r\) 列这样一串前缀的最小距离 \(g[i, s, t]\)。对于 \((x, t_1)\)\((y, t_2)\) 的最小距离其实是 \(\min\{f[x, 0, t_1] + g[y, 0, t_2], f[x, 1, t_1] + g[y, 1, t_2]\}\)

枚举 \(x\) 计算上面那一串和,关键是对于 \(\Delta_y = g[y, 1, t_2] - g[y, 0, t_2]\)\(f[x, 0, t_1] - f[x, 1, t_1]\) 的大小比较,分讨然后求个和即可。

F

还差一步啊,还是差了一些!

面对这个问题分析充要条件,我们将给定的序列倒序那么相当于一个分别以 \(K_1, K_2, \dots, K_M\) 作为最高到最低关键字的稳定排序。很显然,重复出现的 \(K\) 只有第一个会起作用,只要知道这个去重后的结果,而之后出现的一定可以用组合数计算。

先考虑计算去重后的结果,这个时候则必须要涉及到有关 \(A,B\) 次序相关的手段。因为是稳定排序,所以可以唯一确定两个排列中数的对应关系。这里我自己思考的时候犯了一个错,我以为对于任意 \(i < j\) 都要确定先后关系才能唯一确定,实际上不是的,只要确定对于 \(i, (i+1)\) 的先后关系即可。可以把确定出来的关系看作拓扑序的约束,这样就很显然只要知道一条链的约束就可以唯一确定了。

具体而言,令 \(o_i\) 为在原序列中的次序。

  • \(o_i < o_{i+1}\)
    • \(S_1\) 中的数位为 \(B_i\)\(B_{i+1}\) 小的数位,\(S_2\) 中的为 \(B_i\)\(B_{i+1}\) 大的数位。
    • 要求 \(S_1\) 中最早出现的早于 \(S_2\) 中最早出现的,或者 \(S_2\) 中根本没有出现的数。
  • \(o_i > o_{i+1}\)
    • 类似的定义 \(S_1, S_2\),则要求 \(S_1\) 中最早出现的早于 \(S_2\) 中最早出现的,并且强制规定 \(S_1\) 中一定出现过至少一个。

产生了 \(O(n)\) 个约束。这样的计数看上去很难。但是注意到只有“强制规定 \(S_1\) 中至少出现过一个”和上面几个约束本质不同。直接状压对于 \(f[S]\)\(S\) 内满足其它几个约束的排列数。对着最后的 \(S\) 判断“至少在 \(S_1\) 中出现过一次”的约束即可。现在约束都形如 \((S_i, T_i)\)\(S_i\) 最早出现的一定在 \(T_i\) 之前。考虑 \(f[x]\) 转移到 \(f[S + \{x\}]\),如果存在一个 \(x\in T_j\)\(S_i \cap S = \empty\),那么不合法。\(S_i \cap S = \empty \iff S_i\subseteq (U - S)\),反过来我们选择判断此时是否存在 \(x\in T_i\)。这是一个高维前缀或的形式,可以预处理。而“强制规定 \(S_1\) 中至少出现过一个”的约束也可以用类似的高维前缀和解决。

现在问题变成了有 \(t\) 个不同的数,约束好了第一次出现的顺序,问构造成长度为 \(m\) 的序列方案数。设 \(g[i, j]\) 为填了前 \(i\) 个数目前出现了 \(j\) 个数,那么转移很显然是 \(g[i, j] = jg[i - 1, j] + g[i - 1, j - 1]\)。我们惊喜的发现这个东西的递推式和初值和第二类斯特林数一模一样!所以这个东西就是 \(S(m, t) = \sum\limits_{i = 0}^t \dfrac{(-1)^{t - i}i^m}{(t - i)!i!}\),就可以快速计算了。

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 2e5, K = 18;
const int Mod = 998244353;
void upd(int &x, int y) {
	x = ((x + y >= Mod) ? (x + y - Mod) : (x + y));
}
int qpow(int n, int m) {
	int res = 1;
	while(m) {
		if(m & 1) res = 1ll * res * n % Mod;
		n = 1ll * n * n % Mod;
		m >>= 1;
	}
	return res;
}
int n, m, qk;
struct node {
	int type, S1, S2;
} Q[N + 10];
int w[(1 << K) + 10], wc[K + 3][(1 << K) + 10], wf[(1 << K) + 10], tmp[(1 << K) + 10];

ll cp[N + 10], len = 0; vector <int> pos[N + 10];
ll a[N + 10], b[N + 10]; int o[N + 10];

void fwt(int *arr, int n) {
	for(int k = 1; k < (1 << n); k <<= 1) {
		for(int i = 0; i < (1 << n); i += (k << 1)) {
			for(int j = 0; j < k; j++) {
				ll rest = arr[i + j];
				arr[i + j] = rest % Mod;
				arr[i + j + k] = ((rest + arr[i + j + k]) % Mod + Mod) % Mod;
			}
		}
	}
}

int f[(1 << K) + 10];
int sum[K + 10], fac[K + 10], invf[K + 10];
int main() {
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);
	cin >> n >> m >> qk;
	for(int i = 1; i <= n; i++) cin >> a[i], cp[i] = a[i];
	sort(cp + 1, cp + n + 1); len = unique(cp + 1, cp + n + 1) - cp - 1;
	for(int i = 1; i <= n; i++) {
		cin >> b[i];
		a[i] = lower_bound(cp + 1, cp + len + 1, a[i]) - cp;
		pos[a[i]].push_back(i);
	}
	for(int i = n; i >= 1; i--) {
		int d = lower_bound(cp + 1, cp + len + 1, b[i]) - cp;
		o[i] = pos[d].back();
		pos[d].pop_back();
	}

	int U = (1 << qk) - 1;
	for(int i = 1; i < n; i++) {
		int t1[20], t2[20], len1 = 0, len2 = 0;
		for(int j = 1; j <= qk; j++) t1[j] = t2[j] = 0;

		ll x; x = b[i]; while(x) t1[++len1] = x % 10, x /= 10;
		x = b[i + 1]; while(x) t2[++len2] = x % 10, x /= 10;

		if(o[i] < o[i + 1]) Q[i].type = 1;
		else Q[i].type = 2;
		int S1 = 0, S2 = 0;
		for(int j = 1; j <= qk; j++)
			if(t1[j] < t2[j]) S1 |= (1 << (j - 1));
			else if(t1[j] > t2[j]) S2 |= (1 << (j - 1));

		Q[i].S1 = S1, Q[i].S2 = S2;
		w[S1] |= S2;
		if(Q[i].type == 2) wf[S1]++;
	}

	for(int i = 0; i < (1 << qk); i++) {
		for(int j = 0; j < qk; j++)
			if((w[i] >> j) & 1) wc[j][i] = 1;
	}
	for(int j = 0; j < qk; j++) {
		for(int i = 0; i < (1 << qk); i++) tmp[i] = wc[j][i];
		fwt(tmp, qk);
		for(int i = 0; i < (1 << qk); i++) wc[j][i] = tmp[i];
	}
	fwt(wf, qk); reverse(wf, wf + (1 << qk));

	f[0] = 1;
	for(int S = 0; S < (1 << qk); S++) {
		for(int x = 0; x < qk; x++) {
			if(!((S >> x) & 1)) {
				if(!wc[x][U - S])
					upd(f[S + (1 << x)], f[S]);
			}
		}
	}

	fac[0] = 1; for(int i = 1; i <= qk; i++) fac[i] = 1ll * fac[i - 1] * i % Mod;
	invf[qk] = qpow(fac[qk], Mod - 2); for(int i = qk - 1; i >= 0; i--) invf[i] = 1ll * invf[i + 1] * (i + 1) % Mod;
	for(int t = 0; t <= qk; t++) {
		for(int i = 0; i <= t; i++) {
			int t1 = (((t - i) % 2 == 0) ? (1) : (Mod - 1));
			int t2 = qpow(i, m);
			int t3 = 1ll * invf[t - i] * invf[i] % Mod;
			upd(sum[t], 1ll * t1 * t2 % Mod * t3 % Mod);
		}
	}

	int ans = 0;
	for(int S = 0; S < (1 << qk); S++)
		if(!wf[S]) upd(ans, 1ll * f[S] * sum[__builtin_popcount(S)] % Mod);
	cout << ans << '\n';
}

ARC159

C

注意到这个给定的操作非常猎奇,我们希望规定更加简洁的“基本运算”。于是发现:加一次 \(1,2,\dots, n\) 再加一次 \(n, n-1, \dots, 1\) 相当于集体加 \(0\)。交换任意相邻的两个数,相当于一个 \(-1\)\(+1\)。再注意到我们完全可以任意打乱这样的数对的顺序,相当于我们可以规定“基本运算”为给数列中任意两个数分别进行 \(-1, +1\)

只用上述“基本运算”能够构造的充要条件为 \(\sum a_i\)\(n\) 倍数。如果不是,我们不由得猜测它是无解的。应该考量无解的充要条件。

考虑一个必要条件:每次添加的总和不变,如果 \(n\) 为奇数,那么 \(\dfrac{n(n+1)}{2}\)\(n\) 倍数,则要求 \(\sum a_i\) 必须为 \(n\) 倍数才有解,否则无解。如果 \(n\) 为偶数,因为 \(\gcd(\dfrac{n}{2}, n+1) = 1\),因此必然不是 \(n\) 的倍数。接下来我就没想到了。注意到当且仅当 \(x=(\sum a_i \bmod n)\in\{0, \dfrac{n}{2}\}\) 可能有解,这不难理解,分类讨论一下最后加了几个 \(\dfrac{n(n+1)}{2}\) 即可。对于 \(x = 0\) 直接做,\(x = \dfrac{n}{2}\) 则随便加一个排列就变成上面的情况了。

总结:

  • 在构造题中,相信题目给的手段不好用,并且构造一个好用的“基本运算”是好的。
  • 有了基本运算之后,可以从“是否有解”的证明考察这个基本运算是否足够“基本”。
  • 最后还差一步,也算是学到了,诶。

D

很显然,选择一个区间作为结尾,一定会选到它的右端点。设 \(f[i]\) 为以第 \(i\) 个区间结尾的 LIS。若 \(r_j < l_i\) 那么转移从 \(f[j] + (l_i - r_i + 1)\),否则从 \(f[j] + (r_i - r_j)\) 转移,开两颗线段树分别维护即可。

这题一点素质都没有啊,没想明白为啥这个在 D,上一个在 C。我觉得上一个构造真的很牛啊,火大大。

E

非常有趣的题目喔!

有任何手段可以用来刻画这么猎奇的 \(x_i\) 定义吗?从序列的角度显然没有,不过从一颗笛卡尔树的角度来说就可以了。把这个二分过程看成一颗笛卡尔树上行走,那么 \(x_i\) 就是根到这个点的距离。\(|x_i - x_{i - 1}|\) 在这个树上有任何组合意义吗?注意到 \(i, (i+1)\) 是前驱后继的关系,所以一定存在祖先后代关系,于是就是 \(i\)\((i+1)\) 两点的距离。\(\sum\limits_{i = c}^{d - 1}|x_i - x_{i+1}|\) 可以联想到“给定点集树上联通块”的经典结论,只需要再求出 \(\operatorname{dist}(c, d)\) 即可。这也是简单的。

#include <bits/stdc++.h>
#define ll long long 
using namespace std;
const int N = 100;
int m;
ll n, a[N + 3], b[N + 3];
ll Abs(ll x) {
	return ((x < 0) ? (-x) : x);
}
ll dist(ll i) {
	ll l = 1, r = n; ll t = 0;
	while(l <= r) {
		ll mid = ((1ll * a[t % m] * l + 1ll * b[t % m] * r) / (a[t % m] + b[t % m]));
		if(i < mid) r = mid - 1;
		else if(i > mid) l = mid + 1;
		else return t;
		t++;
	}
	return t;
}

void print(ll s, ll t, int dep) {
	ll mid = ((1ll * a[dep % m] * s + 1ll * b[dep % m] * t) / (a[dep % m] + b[dep % m]));
	cout << s << ' '<< t << ' ' << mid << '\n';
	if(s < mid - 1) print(s, mid - 1, dep + 1);
	if(mid + 1 < t) print(mid + 1, t, dep + 1);
}
bool check(ll x, ll l, ll r) {
	return (l <= x && x <= r);
}
ll getlca(ll A, ll B, ll s, ll t, int dep) {
	ll mid = ((1ll * a[dep % m] * s + 1ll * b[dep % m] * t) / (a[dep % m] + b[dep % m]));
	int c1 = check(A, s, mid - 1) + check(B, s, mid - 1);
	int c2 = check(A, mid + 1, t) + check(B, mid + 1, t);
	if(c1 != 2 && c2 != 2) return dep;
	else if(c1 == 2) return getlca(A, B, s, mid - 1, dep + 1);
	else return getlca(A, B, mid + 1, t, dep + 1);
}
ll qry(ll ql, ll qr, ll s, ll t, int dep, bool fc) {
	if(ql <= s && t <= qr)
		return t - s;
	ll mid = ((1ll * a[dep % m] * s + 1ll * b[dep % m] * t) / (a[dep % m] + b[dep % m]));
	if(ql > mid) return qry(ql, qr, mid + 1, t, dep + 1, fc) + fc;
	if(qr < mid) return qry(ql, qr, s, mid - 1, dep + 1, fc) + fc;

	ll rest = 0;
	if(ql < mid) rest += qry(ql, qr, s, mid - 1, dep + 1, 1) + 1;
	if(qr > mid) rest += qry(ql, qr, mid + 1, t, dep + 1, 1) + 1;
	return rest;
}

int main() {
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);
	cin >> n >> m;
	for(int i = 0; i < m; i++) cin >> a[i] >> b[i];
	// print(1, n, 0);
	int q; cin >> q;
	while(q--) {
		ll c, d; cin >> c >> d;

		ll w = qry(c, d, 1, n, 0, 0);
		ll v = dist(d) + dist(c) - 2 * getlca(c, d, 1, n, 0);
		cout << w * 2 - v << '\n';
	}
}

F

ARC160

C

这 tm 才 *1861?我请问了。

考虑判定毫无前途,考虑从小到大构造这个集合。设 \(f[i, j]\) 为目前合并出的最大值是 \((i+1)\)\(j\) 个的方案数,那么转移有 \(f[i, \lfloor\frac{c_{i} +j}{2}\rfloor] = \sum\limits_{k = j}^n f[i - 1, k]\)

这乍一看是 \(O(n^2)\) 的,我们精细分析下,令 \((i - 1)\) 时刻 \(j\) 值域为 \([V_1, V_2]\),那么 \(i\) 时刻 \(j\) 的值域就为 \([\frac{c_{i}}{2}, \frac{V_2 + c_{i}}{2}]\),初始值域为 \([0, 0]\)。这样一共会进行 \(O(n)\) 轮。第一轮值域 \([\frac{c_1}{2}, \frac{c_1}{2}]\),第二轮 \([\frac{c_2}{2}, \frac{c_1}{4} +\frac{c_2}{2}]\),以此类推,最后值域大小总和是 \(\sum\limits c_i(1+\frac{1}{2}+\frac{1}{4} + \dots)\),也就是 \(O(n)\) 级别的。

实现上第一维滚动掉,记录每一轮第二维值域,维护前缀和。

#include <bits/stdc++.h>
using namespace std;
const int N = 3e5 + 30, Mod = 998244353;
void upd(int &x, int y) {
	x = ((x + y >= Mod) ? (x + y - Mod) : (x + y));
}
int a[N + 10], n, c[N + 10];
int f[N + 10], g[N + 10], h[N + 10];

int main() {
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);
	cin >> n;
	for(int i = 1; i <= n; i++) cin >> a[i], c[a[i]]++;

	f[0] = 1;
	int V1 = 0, V2 = 0;
	for(int i = 1; i <= N; i++) {
		if(!V1) h[0] = f[0]; else h[V1 - 1] = 0;
		for(int j = max(V1, 1); j <= V2; j++)
			h[j] = h[j - 1], upd(h[j], f[j]);
		for(int j = 0; j <= V2; j++)
			upd(g[(c[i] + j) / 2], (h[V2] - ((j <= V1) ? 0 : (h[j - 1])) + Mod) % Mod);
		V1 = c[i] / 2, V2 = (V2 + c[i]) / 2;
		for(int j = V1; j <= V2; j++) {
			f[j] = g[j];
			g[j] = 0;
		}
	}

	int sum = 0;
	for(int i = V1; i <= V2; i++) upd(sum, f[i]);
	cout << sum << '\n';
}

总结:

  • 实际上思路应该是从考虑判定变成考虑对合法序列的构造。构造的时候,因为合并是从小到大进位的,所以可以从小的位向大的位来考虑。
  • 这个值域分析???只能说写出来暴力 dp 后再修正吧,这也提醒我们,不要急于否定一个看上去很暴力的 dp,或者说一个 \(N = 2e5\) 但是二次的暴力!

D

看到这个 998244353 啪的一下我九点进来了很快啊然后我就不会了。

直接考虑判定?这也太难了,考虑构造。直接构造会产生重复,怎么办?首先倒转以下变成从全 \(0\) 序列加到 \(A\) 序列。钦定顺序,首先进行 2 操作,然后进行 1 操作。注意到如果同一个区间被加了超过 \(K\) 次不如用 \(1\) 操作,所以我们规定每个区间最多只会加 \(K\) 次。我们得到了这样一个构造策略,但是不知道一个构造策略和最终序列是否构成双射关系,证明证明:

  • 显然一个构造策略只会指向一个最终序列,我们证明最终序列到构造策略。
  • 对于 \(A_1\) 只会进行小于 \(K\) 次的二操作,也就是说 \(A_1\) 处进行的 \(2\) 操作应该唯一\(A_1 \bmod k\) 次。删除 \(A_1\),那么对于 \(A_2\) 也是同理,归纳法以此类推。

于是问题变成了:\(\sum\limits_{i = 1}^{2n - k + 1}x_i = \dfrac{M}{k}\),要求 \(\forall i \in [1, n - k + 1], b_i < k\)。这个经典的方程带上界,用容斥解,最终解数量为

\[\sum\limits_{t = 0}^{n -k+1}(-1)^t\binom{\frac{M}{k}+2n-(t+1)k}{2n - k}\binom{n - k + 1}{t} \]

总结:

  • 对于计数不能拘泥于“判定”,还可以考虑从构造出发,建立双射!
  • 建立双射可以从归纳法的角度考虑证明。
  • 这题还要很大胆的猜测。
  • 两道题都是不考虑判定,考虑构造?但是考虑构造也有不同的地方。
    • 这道题是先反方向将删除变成从 \(0\) 开始增加,然后直接表达出构造方案,证明是双射。
    • 上一道题则是按照顺序,划分出子问题,一步一步构造。
posted @ 2025-11-03 20:00  CatFromMars  阅读(20)  评论(1)    收藏  举报