CF1605F PalindORme
posted on 2024-08-15 13:05:24 | under | source
先只考虑偶数。要计数,先判定,构造单射,令 \(b_i=a_{p_i}\)。起始情况选取两个相等的数填到 \(b_1,b_n\),然后假设已经确定 \(b_1\dots b_{i-1},b_{n-i+2}\dots b_n\),设 \(S\) 为或之和,那么选取的两个数 \(x,y\) 合法,当且仅当 \(S\) 为 \(0\) 的那些位上 \(x,y\) 相等。
这相当于填写一个回文序列,只不过每次忽略掉一些位。注意一下,假如某一次填写有多种选择,随便选一种即可,因为其它选择之后也必然合法。
然后考虑不完美序列与完美序列的联系,可以发现,两者互为补集,同时不完美序列由 \([1,i],[i+1,n-i],[n-i+1,n]\) 构成,其中 \([1,i]+[n-i+1,n]\) 是完美序列,\([i+1,n-i]\) 由忽略某些位后互不相等的元素构成。
于是设计状态 \(f_{i,j}\),表示已经填写 \(b_1\dots b_i,b_{n-i+1}\dots b_n\),恰好涉及 \(j\) 位,对应的 \(a_1\dots a_n\) 的不同方案数。
\(b\to a\) 并不构成单射,所以要着重关注去重。
然后设计辅助状态 \(all_{i,j},dif_{i,j}\),分别表示总集、互不相等方案数。不难容斥得出。
然后有转移:
解释:\(i,j\) 不能取到 \(n,k\) 否则会出错。组合数在 \(a\) 中选择若干位置和二进制位作为完美序列部分,总集减 \(f\) 是求这些位置的完美序列,最后是互不相等部分,二次方部分是为互不相等部分的数确定被忽略的二进制位的取值。
看起来很完美,不是吗?考察一下这样做会不会算重(显然不会算少)。考虑完美序列部分与互不相等部分,我们必须满足两者没有相同的元素,才能使用 \({n\choose i}\) 将两者组合为一体,否则映射到 \(a\) 上就会算重。
记完美序列部分设计到的位为本原位,其余的是新增位。
由于互不相等部分的新增位有值,而完美序列部分此处无值,所以不会算重。但是!这题毒瘤的地方来了,\(a\) 可以填 \(0\)!所以就会导致两个部分有交于是算重。
怎样解决呢?观察到互不相等部分至多有一个 \(0\),这启示我们按是否有 \(0\) 分类转移。
一种好写的解决方案是修改完美序列的定义。若长度为偶数,保留原有定义。若为奇数,就让它必须存在一个 \(0\),且除掉一个 \(0\) 后为完美序列(是题面的定义)。同理能推出 \(f\) 的定义。
然后让 \(dif\) 不能填 \(0\),这样转移枚举的两部分不可能相交了。
然后解释下修改后的转移:
-
偶数长度:假如枚举的两部分为偶数,那么就能求出不存在 \(0\) 的方案;否则就是求有 \(0\) 方案,可以理解为让奇完美序列保留的那个 \(0\) 加到互不相等序列中(显然加完后依旧互不相等)。
-
奇数长度:此时 \(f\) 的定义为互不相等部分长度大于 \(1\)(即题面定义的不完美序列)或互不相等部分长度为 \(1\) 且不为 \(0\)。那么当枚举的互不相等部分大于 \(1\) 时就是对应了前者,否则对应了后者。
这样就不会算重了!更好的是,转移没有发生变化,仅仅是多考虑了奇数并修改 \(dif\) 的定义。
然后答案是 \(\sum\limits_{i=0}^k {k\choose i}f_{n,i}\)。
假如 \(n\) 为奇数怎么办?根据题面定义,那么 \(f_{n,i}\) 不应该包含“互不相等部分长度为 \(1\) 且不为 \(0\)” 这部分,因为剩下的一个元素想填啥就填啥。所以此时不要转移即可。即 \(i=n-1\) 且 \(n\) 为奇数时不转移。
代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define ADD(a, b) a = (a + b) % mod
const int N = 1e2 + 5;
int T, n, m, mod, ans;
int jc[N], jcinv[N];
int all[N][N], dif[N][N], f[N][N], bas[N * N];
inline int qstp(int a, int k) {int res = 1; for(; k; a = a * a % mod, k >>= 1) if(k & 1) res = res * a % mod; return res;}
inline int C(int n, int m) {return (n < 0 || m < 0 || n < m) ? 0 : jc[n] * jcinv[m] % mod * jcinv[n - m] % mod;}
signed main(){
scanf("%lld%lld%lld", &n, &m, &mod);
jc[0] = jcinv[0] = bas[0] = 1;
for(int i = 1; i < N; ++i) jcinv[i] = qstp(jc[i] = jc[i - 1] * i % mod, mod - 2);
for(int i = 1; i < N * N; ++i) bas[i] = bas[i - 1] * 2 % mod;
memset(all, 0, sizeof all), memset(dif, 0, sizeof dif), memset(f, 0, sizeof f);
for(int i = 0; i <= n; ++i)
for(int j = 0; j <= m; ++j)
for(int k = 0, fl = 1; k <= j; ++k, fl = mod - fl){
int res = fl * C(j, k) % mod, cnt = 1;
ADD(all[i][j], res * bas[i * (j - k)] % mod);
for(int p = bas[j - k] - 1, lby = 1; lby <= i; ++lby, p = (p + mod - 1) % mod) cnt = cnt * p % mod;
ADD(dif[i][j], res * cnt % mod);
}
all[0][0] = 1;
for(int i = 0; i <= n; ++i)
for(int j = 0; j <= m; ++j)
for(int k = 0; k < i; ++k)
for(int p = 0; p < j; ++p){
if(k == n - 1 && n % 2 == 1) continue;
ADD(f[i][j], C(i, k) * C(j, p) % mod * (all[k][p] - f[k][p] + mod) % mod * dif[i - k][j - p] % mod * bas[(i - k) * p] % mod);
}
for(int j = 0; j <= m; ++j) ADD(ans, (all[n][j] - f[n][j] + mod) * C(m, j) % mod);
cout << ans;
return 0;
}

浙公网安备 33010602011771号