CF2056F2 Xor of Median (Hard Version)

· · 题解

题意:

定义一个序列是好的,当且仅当:

求所有长度为 N,值域在 [0,m) 的好的整数序列的中位数的异或和。中位数定义为排序后第 \lfloor\frac{n+1}{2}\rfloor

**题意:** 注意到这题求的是异或和,这意味着如果某个方案出现了偶数次相当于没有出现。 序列是否有序不影响好坏,但是计数的时候是有序的。 对于所有出现的数,不妨设出现次数是 $cnt_1,\dots,cnt_k$,方案数是: $$ \binom{N}{cnt_1,cnt_2,\dots,cnt_k} $$ 如果这个东西是偶数,那么显然是不会计入答案的。 这是一个多项式系数,可以表示为若干个二项式系数的乘积,也就是组合数: $$ \binom{N}{cnt_1}\binom{N-cnt_1}{cnt_2}\dots \binom{N - cnt_1-cnt_2-\dots,-cnt_{k-1}}{cnt_k} $$ 奇偶性相当于对 $2$ 取模,而组合数对小质数取模考虑 Lucas 定理。 如果最终是奇数,就要求 $cnt_1$ 在二进制每一位上都 $\le N$ 的每一位,$cnt_2$ 在二进制每一位上都 $\le N-cnt_1$ 的每一位,以此类推。 而注意到 $\sum_i cnt_i = N$,所以这个限制本质上就是 $cnt_1,cnt_2,\dots,cnt_k$ 构成了 $N$ 的所有 $=1$ 的二进制位的划分。那么显然出现次数最多的数严格大于一半,所以中位数就是出现次数最多,也就是最大的那个数。 这样就非常好做了,也成功理解了题目为什么给我们 $N$ 的二进制表示。不妨设 $n$ 表示 $N$ 的二进制下 $=1$ 的位置的个数, 先不管复杂度,我们考虑枚举 $0 \le k < m$,设计一个 dp:$f(i,j,x)$ 表示当前分完了前 $i$ 位,当前所有有 $j$ 个数,最小的一个是 $x$。 如果这一位是 $0$,则直接 $i \to i+1$ 复制过去,否则,有两种可能: - $j$ 个数中某个数拿走,由于是二进制,所以不会影响大小关系。$f(i,j,x) \to j \times f(i+1,j,x)$。 - 新增一个 $< x$ 的数,$f(i,j,x) \to f(i+1,j,t)(t < x)$。 这样 dp 是 $O(nm^3)$,因为还要枚举 $k$,连 F1 都过不了。 但是注意到这很没必要,每个 $k$ 都要单独计算,所以我们把状态反过来定义,$f(i,j,x)$ 表示前 $i$ 位之后的都确定了,前 $i$ 位有 $j$ 个数,最小的是 $k$。 这样就变一次 $dp$ 即可统计答案,时间复杂度 $O(nm^2)$。可以过掉 F1。 但是 F2 的数据范围很大,我们上面的做法枚举了 $k$,这是 $O(m)$ 的,显然无法接受,我们考虑如何将这个 $k$ 融入到 dp 的贡献中。 注意到实际上 $j$ 的范围是 $n$ 而不是 $m$,这意味着实际上我们可以将 $k,j$ 相同的视作一种方案,最后乘上 $\binom{k}{j-1}$ 即可。 我们发现一个事:实际上 dp 的时候 $x$ 不影响转移系数,我们考虑化成两维:设 $f(i,j)$ 表示前 $i$ 位之后已经确定,前 $i$ 位有 $j$ 个数的总贡献是多少。 转移类似: - 当 $j$ 是奇数的时候,可以 $f(i+1,j) \to f(i+1,j)$。 - 也可以 $f(i+1,j+1) \to f(i+1,j)$。 关键是如何设定初值? 注意到对于初始固定了要选 $j$ 个数的方案,$k$ 可行当且仅当 $\binom{k}{j-1}$ 是奇数,如果是奇数我们才计入贡献。 这样 dp 就是 $O(nm)$ 的了,但是依然和 $m$ 有关。 那么我们需要将初值和 dp 两部分分别优化。 观察这个 dp 的式子,我们发现从 $j$ 的变化是这样的:每一次 $j$ 是偶数只能变成 $j+1$,否则可以变成 $j,j+1$ 中的一个,最终会变成某个 $k$。 也就是 $n$ 长度的一个序列:$1,1,1,2,3,3,3,4,5,5,5,\dots$ 这样的。 注意到 $1 \sim k$ 一定至少出现一次,而奇数还可以多出现几次,设有 $t$ 个奇数,则相当于求解不定方程 $x_1+x_2+\dots+x_t = n - k$,这个可以组合数直接计算,注意到我们只关心奇偶性,所以可以直接拆位 $O(\log n)$ 计算。 所以如果我们能求出初值,我们也能求出最终的方案,$O(n)$ 枚举一共选了几个数即可。 现在我们考虑怎么求初值,相当于求 $0 \le t < m,\binom{t}{j-1}$ 为奇数的 $t$ 的异或和。我们注意到这就是要求 $t$ 的每一位都 $\ge j-1$ 的每一位,我们可以直接数位 dp,设 $f(i,0/1),g(i,0/1)$ 分别表示前 $i$ 位是否贴着上界的方案数和总贡献,转移的时候如果某一位填了 $1$ 并且前面的方案数是奇数就可以给贡献加上这一位。 这样我们就能在 $O(\log m)$ 的时间内计算出初值,从而在 $O(n \log m)$ 的时间内解决这个问题。 ```cpp #include <iostream> #include <cstdio> #include <vector> #include <cstring> #include <algorithm> using namespace std; const int N = 2e5 + 5; int C(int n, int m) {//计算模 2 的余数 if (n < m) return 0; if (n == 0 || m == 0) return 1; for (int j = 20; j >= 0; j--) if ((n >> j & 1) < (m >> j & 1)) return 0; return 1; } int CC(int n, int m) {//总和为 n,m 个变量 return C(n + m - 1, m - 1); } const int k = 30; int n, m; int num[2][35] = {{0}}; int f[35][2] = {{0}}; int g[35][2] = {{0}}; void add(int &x, int y) { x ^= y; } int cal(int t) { for (int j = k; j >= 1; j--) num[1][j] = (t >> (j - 1) & 1); memset(f, 0, sizeof f), memset(g, 0, sizeof g); f[0][0] = 1; for (int j = 1; j <= k; j++) { //转移 f[j][0] if (f[j - 1][0]) add(g[j][0], (1 << (j - 1))); if (num[1][j]) { add(f[j][0], f[j - 1][0]); add(g[j][0], g[j - 1][0]); } //转移f[j][1] if (num[0][j]) {//一定可以填 1 并且一定贴上界 add(f[j][1], f[j - 1][1]); add(g[j][1], g[j - 1][1]); if (num[0][j] && f[j - 1][1]) add(g[j][1], (1 << (j - 1))); if (!num[1][j]) { add(f[j][1], f[j - 1][0]); add(g[j][1], g[j - 1][0]); } } else if (num[0][j] == num[1][j]) { add(f[j][1], f[j - 1][1]); add(g[j][1], g[j - 1][1]);//填 0 } // cout << j << " ?? " << f[j][0] << " g:" << g[j][0] << " ?? f: " << f[j][1] << " g: " << g[j][1] << endl; } return g[k][1]; } void slv() { scanf("%d%d", &n, &m); int t = 0; for (int i = 0, x; i < n; i++) scanf("%1d", &x), t += x; for (int j = k; j >= 1; j--) num[0][j] = (m >> (j - 1) & 1); n = t; int ans = 0; for (int i = 1; i <= n; i++) if (CC(n - i, (i + 1) / 2)) ans ^= cal(i - 1);//i - 1的所有贡献 printf("%d\n", ans); } int main() { int T; scanf("%d", &T); while (T--) slv(); return 0; } /* 1 2 3 10 */ ```