CF1605F
用一种更加方便的方式刻画合法序列,我们发现每一个合法序列都能通过以下操作删至不超过一个数,同样一个不合法序列一定不行。
- 令变量 \(V=0\)
- 从序列中选出两个数 \(x,y\) 满足 \(x\ \text{or}\ V=y\ \text{or}\ V\)
- 令 \(V\gets x\ \text{or}\ V\),删去 \(x,y\) 并回到第二步
一个合法序列一定不会被判为不合法序列是因为你每一步没有选择的合法 \((x,y)\) 在之后的步骤一定能继续用。
我们发现利用这个性质仍然不好对合法串计数,但是可以尝试通过这一操作搭建合法序列与不合法序列之间的桥梁,然后容斥算出其对应合法序列数量。
定义一个不合法序列的最优子序列为其经过操作后移除的序列(若存在一个和 \(V\) 的按位与等于自己的数,就将其放在最优子序列的中间)
可以发现一个不合法序列剩下的数都满足,去掉 \(V\) 为 \(1\) 的二进制位,任意数不为 \(0\) 且两两不同,于是我们建立了一个不合法序列与合法序列(序列内部无序)之间的映射,其中合法序列长度、二进制位数量均小于不合法序列。
接下来就是正常流程了,令 \(f_{i,j}\) 为长度为 \(i\),二进制位为 \(1\) 数量恰好为 \(j\) 的序列数量,\(g_{i,j}\) 为长度为 \(i\),二进制位为 \(1\) 数量恰好为 \(j\) 的不合法序列数量。
\(f_{i,j}\) 可以容斥预处理,也就是:
\(g_{i,j}\) 可以直接枚举其最优子序列的长度与二进制位个数(注意这里的最优子序列需满足长度为偶数/长度为奇数且中间的数为 \(0\)),然后有:
前面两个组合数的系数很显然,二的幂次的系数是因为被删去的二进制位可以任意放,\(h_{i,j}\) 是长度为 \(i\),二进制位为 \(1\) 的数量恰好为 \(j\),数字非零且两两不同的方案数,容斥计算即可:
发现当 \(i=n\) 且 \(n\) 是奇数时,此时的不合法序列是无法从它长度减一的合法序列转移过来的,因为这是需要被统计进答案的。
感觉说的有些前言不搭后语(
还是举个例子可能清楚点,比如 \(1,3,1\) 这个序列,在答案里是要被统计进去的,但是在在另外一个限制下(长度为奇数的好序列,中间那个数必须是 \(0\)),这个不是好序列,那么它是不合法序列,它唯一对应的序列是 \(1,1\)。
其实相当于前面求的好序列和答案要求的好序列有一些区别。
然后就做完了,复杂度 \(\mathcal O(n^2k^2)\)。
Code:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 85;
int n, m, mod;
int pw2[N*N];
int f[N][N], g[N][N], dp[N][N], C[N][N];
int A(int n, int m) {
if (n < m) return 0;
int res = 1;
for (int i = n; i >= n - m + 1; --i) res = 1ll * res * i % mod;
return res;
}
inline void add(int &a, int b) {
a += b;
if (a >= mod) a -= mod;
if (a < 0) a += mod;
}
void init(int n) {
for (int i = 0; i <= n; ++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;
}
pw2[0] = 1;
for (int i = 1; i <= n * n; ++i) pw2[i] = pw2[i - 1] * 2 % mod;
for (int i = 0; i <= n; ++i)
for (int j = 0; j <= n; ++j)
for (int k = 0; k <= j; ++k) {
int ty = ((j - k) & 1) ? -1 : 1;
add(g[i][j], 1ll * ty * pw2[i * k] * C[j][k] % mod);
add(f[i][j], 1ll * ty * A(pw2[k] - 1, i) * C[j][k] % mod);
}
}
int main() {
scanf("%d%d%d", &n, &m, &mod);
init(80);
for (int i = 1; i <= n; ++i)
for (int j = 0; j <= m; ++j)
for (int k = 0; k < i; ++k)
for (int l = 0; l < j; ++l) {
if ((n & 1) && (i == n) && (k == i - 1)) continue;
add(dp[i][j], 1ll * ((g[k][l] - dp[k][l] + mod) % mod) * C[i][k] % mod * C[j][l] % mod * f[i - k][j - l] % mod * pw2[l * (i - k)] % mod);
}
int ans = 0;
for (int i = 0; i <= m; ++i) add(ans, 1ll * C[m][i] * ((g[n][i] - dp[n][i] + mod) % mod) % mod);
printf("%d", ans);
return 0;
}