Codeforces 1605F. PalindORme (2900)
题目描述
定义 \(a\) 序列是一个 \(\rm{PalindORme}\) 当且仅当 \(\forall i\in[1,n],(a_1\ \text{or}\ a_2\ \text{or}\cdots\text{or}\ a_i)=(a_{n-i+1}\ \text{or}\cdots\ \text{or}\ a_{n-1}\ \text{or}\ a_n)\)。
称一个序列 \(a\) 是好的当且仅当存在一个 \(1\sim n\) 的排列 \(p\),使得 \(a_{p_1},a_{p_2},\cdots,a_{p_n}\) 是一个 \(\rm{PalindORme}\)。
求长度为 \(n\),序列中的每个数的值域在 \([0,2^k)\) 中的好的序列个数。
两个序列 \(a,b\) 不同当且仅当存在至少一个 \(i\), \(a_i\neq b_i\)。
\(n,k\le 80\)。
考虑一个好的序列怎样组成 \(\rm{PalindORme}\)。
首先序列一定满足 \(a_1=a_n\),其次能找出 \(x,y\),使得 \(a_1\ \text{or}\ x=a_n\ \text{or}\ y\)。记前缀 \(\text{or}\) 值为 \(val\)。并且还能一直找到 \(x,y\),使得 \(val\ \text{or}\ x=val\ \text{or}\ y\)。直到序列还剩不超过 \(1\) 个元素。
如果在某个未结束的时刻没有找到满足条件的 \(x,y\),那么此序列不是好的序列。再考虑若存在多对 \((x_i,y_i)\) 使得 \(val\ \text{or}\ x_i=val\ \text{or}\ y_i\),如何决定先后顺序。
显然先后顺序不影响,因为若 \(val\ \text{or}\ a=val\ \text{or}\ b,val\ \text{or}\ c=val\ \text{or}\ d\),那么可知 \(val\ \text{or}\ a\ \text{or}\ c=val\ \text{or}\ b\ \text{or}\ d\)。所以对 \((a,b)\) 先操作和对 \((c,d)\) 先操作并不影响。证明可以考虑增加的二进制表示中的 \(1\)。
设 \(S\) 为序列 \(a\) 中元素的可重集合,则可以用以下的伪代码判断是否可以组成 \(\rm{PalindORme}\)。
val = 0
good = true
while (|S| > 1)
if 存在 x,y 使得 val | x = val | y
val = val | x
从 S 中删除 x,y
else good = false
通过以上分析也可以知道一个不好的序列的算法流程:
- 在某次操作后,没法再添加 \(2\) 个元素到序列中。
- 由于每次都是添加两个元素到序列中,所以无法添加时形成的 \(\rm{PalindORme}\) 是该不好的序列能组成的最大偶数长度的好的序列。
- 此时序列中最多再存在一个元素 \(x\),使得 \(x\ \text{or}\ val=val\)。因为假设超过 \(1\) 个,则可以再加入一对进入 \(\rm{PalindORme}\)。
- 如果存在,则将此元素也加入 \(\rm{PalindORme}\),只需要将其放在 \(\rm{PalindORme}\) 中心位置即可。
- 将当前形成的 \(\rm{PalindORme}\) 记作该不好序列的最佳子序列,不好的序列中的剩余元素都存在至少一个二进制位不被 \(val\) 包含。
- 所以每个长度为 \(n\),有 \(k\) 个二进制位的不好的序列都可以映射到一个长度为 \(n'<n\),有 \(k'<k\) 个二进制位的好的序列。
现在就可以使用一个坏序列的最佳子序列计算坏序列。由于我们只关心不同状态的二进制位的数量而不是具体哪几位,所以可以设 \(bad_{i,j}\) 表示长度为 \(i\) 的,有恰好 \(j\) 个二进制位的坏序列的数量。
其中 \(f(i,j,i',j')\) 表示长度为 \(i\) 的,有恰好 \(j\) 个二进制位的坏序列的最佳子序列是长度为 \(i'\) 的,有恰好 \(j'\) 个二进制位的好序列的坏序列的数量。
补集转化后也就是
\(total_{i,j}\) 是长度为 \(i\) 的,有恰好 \(j\) 个二进制位的所有序列数,可以通过容斥求出,此时转移的复杂度是 \(O(n^4)\),关键在于计算 \(f(i,j,i',j')\):
- 在 \(i\) 个位置中选出 \(i'\) 个位置放进最佳子序列,该部分方案数为 \(\binom{i}{i'}\)。
- 在 \(j\) 个二进制位中选出 \(j'\) 个二进制位作为最佳子序列有的二进制位个数,方案数为 \(\binom{j}{j'}\)。
- 除了最佳子序列以外的 \(i-i'\) 个其他元素有恰好 \(j-j'\) 个二进制位,且每个元素与 \(val\) 或起来的值都互不相同的方案数。将方案数拆成两部分:
- 考虑有 \(i-i'\) 个不同正整数,有恰好 \(j-j'\) 个二进制位的方案数。先考虑有最多 \(j-j'\) 个二进制位,方案数即是 \(\binom{2^{j-j'}-1}{i-i'}\times (i-i')!\)。再容斥计算出恰好 \(j-j'\) 个二进制位的方案数。
- 考虑 \(i-i'\) 的原本 \(j'\) 位的方案数。由于 \(val\) 中已经包含这 \(j'\) 位,所以每个元素任意一种都合法,方案数也就是 \(2^{(i-i')j'}\)。
但是,按照这样计算,在长度为奇数时会出现错误。例如 \([1,2,1]\) 是好的序列,但 \(dp\) 时会将其算成坏序列。也就是对于两两添加进好序列后还剩一个元素会出现问题,那么只需要特判,当 \(n\) 为奇数时不能从长度为 \(n-1\) 的好序列转移到长度为 \(n\) 的坏序列。
答案就是
时间复杂度 \(O(n^4)\)。
\(\color{blue}{\text{code}}\)
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 81, M = 6401;
int n, m, mod, ans, pw[M], C[N][N], f[N][N], tot[N][N], bad[N][N];
inline int down(int x, int y) {
int ans = 1;
for (int i = x; i >= x - y + 1; -- i) ans = (ll)ans * i % mod;
return ans;
}
int main() {
scanf("%d%d%d", &n, &m, &mod), pw[0] = 1;
for (int i = 1; i < 6401; ++ i) pw[i] = pw[i - 1] * 2 % mod;
for (int i = 0; i < 81; ++ i) {
C[i][0] = 1;
for (int j = 1; j <= i; ++ j) C[i][j] = (C[i - 1][j] + C[i - 1][j - 1]) % mod;
}
for (int i = 0; i <= n; ++ i)
for (int j = 0; j <= m; ++ j)
for (int k = 0; k <= j; ++ k) {
int coef = (ll)C[j][k] * pw[i * k] % mod;
(tot[i][j] += (j - k) & 1 ? mod - coef : coef) %= mod;
coef = (ll)C[j][k] * down(pw[k] - 1, i) % mod;
(f[i][j] += (j - k) & 1 ? mod - coef : coef) %= mod;
}
for (int i = 0; i <= n; ++ i)
for (int j = 0; j <= m; ++ j)
for (int a = 0; a < i; ++ a)
for (int b = 0; b < j; ++ b) {
if ((n & 1) && i == n && a == n - 1) continue;
(bad[i][j] += (ll)C[i][a] * C[j][b] % mod * f[i - a][j - b] % mod * (tot[a][b] - bad[a][b] + mod) % mod * pw[(i - a) * b] % mod) %= mod;
}
for (int i = 0; i <= m; ++ i)
(ans += (ll)C[m][i] * (tot[n][i] - bad[n][i] + mod) % mod) %= mod;
return printf("%d\n", ans), 0;
}