P13275 [NOI2025] 集合 题解

· · 题解

我虽然没能场切此题,但是也获得了很高的分数,算是拯救了我整个 Day 2 了。

首先有 O(8^n) 的 DP。设 f_{i,j,k},表示前 i 个数的情况确定了,使得 f(P) = jf(Q) = k 的方案数。有如下转移:

f_{i,j,k} \times a_i \rightarrow f_{i+1,j \cap (i+1),k} f_{i,j,k} \times a_i \rightarrow f_{i+1,j,k \cap (i+1)} f_{i,j,k} \rightarrow f_{i+1,j,k}

到此为止就可以集合幂级数暴做了。

每次转移就是 \operatorname{and} 卷积。考虑 FMT 之后的 [x^j y^k]。每次相当于点乘 FMT 后的 x^i + y^i + 1。也就是乘上 a_i \times ([j \subseteq i] + [k \subseteq i]) + 1。这样下来我们可以轻松算出一个 [x^j y^k] 处的值 g_{j,k}

设:

ss_i = \prod\limits_{i \subseteq j} (2a_j + 1),s_i = \prod\limits_{i \subseteq j} (a_j + 1)

那么有:

g_{j,k} = \dfrac{s_j s_k ss_{j \cup k}}{s_{j \cup k} ^ 2}

意思就是同时是 j,k 超集的需要乘 2a_i + 1,只是一个的超集的要乘 a_i + 1

可能会除以 0,只需要记录 c_i 表示 i 超集中 0 因子的数量,然后判断是否有 c_j = c_k = c_{j \cup k} 即可。

至此我们可以 O(2^n n) 预处理,O(1) 查询 g_{j,k} 一个位置的值。全部查一遍再 IFMT 回来需要 O(4^n n) 的复杂度。可以获得 36 分。

我们发现最后其实只用了 f_{i,i} 的值,考虑把 IFMT 拆开式子:

\begin{align*} f_{i,i} & = \sum\limits_{i \subseteq j,i \subseteq k} (-1)^{\left|j-i\right| + \left|k-i\right|}g_{j,k} \\ & = \sum\limits_{i \subseteq j,i \subseteq k}(-1)^{\left|j\right|+\left|k\right|}g_{j,k} \end{align*}

这相当于对于 g_{j,k},有 2^{\left|{j \cap k}\right|}i 统计到了他。因此答案就是:

\begin{align*} ans & = \sum\limits_{j,k} (-1)^{\left|j\right|+\left|k\right|}2^{\left|{j \cap k}\right|}g_{j,k}\\ & = \sum\limits_{j,k} (-2)^{\left|j\right|+\left|k\right|}2^{- \left|{j \cup k}\right|} \dfrac{s_j s_k ss_{j \cup k}}{s_{j \cup k} ^ 2} \end{align*}

注意这里用到了 \left|j\cap k\right| = \left|j\right| + \left|k\right| - \left|j \cup k\right|

这已经变成了或卷积的形式!不考虑 a_i = mod - 1 的情况,可以直接做到 O(2^n n),拼上暴力获得 68 分。

我赛时的想法就止步于此。a_i = mod - 1 时我们该怎么办呢?

一个想法是根据上面暴力的结论,只有 c_i 相同的才能互相转移,因此按照 c_i 分类分别跑 FMT。但是这个复杂度是错的。A 性质也只能过一个点。

但是 c_i 还有一个性质就是如果 i \subseteq j,那么 c_i > c_j,而 FWT 时我们只会和自己的子集进行运算。所以 i 存在一个子集 j 满足 c_j > c_ij 的子集 k 也一定满足 c_k > c_i 所以 j 的一切信息对于 i 就没用了。因此我们只需要在 c_j = c_i 的时候才转移即可。类似于其他题解说的只保留低次项。

可以获得 100 分。

### 代码 ```cpp inline void Add(int &a,int b){(a+=b)>=mod?a-=mod:1;} inline void FWT(int *a,int n,int x){ for(int t=2,k=1;t<=n;t<<=1,k<<=1) for(int i=0;i<n;i+=t) for(int j=0;j<k;j++){ if(c[i+j]==c[i+j+k])Add(a[i+j+k],1ll*a[i+j]*x%mod); } } inline void Solve(){ rd(n); V=(1<<n); for(int i=0;i<V;i++)rd(a[i]); for(int i=0;i<V;i++){ s[i]=a[i]+1;c[i]=0; ss[i]=(2*a[i]+1)%mod; if(a[i]==mod-1)s[i]=c[i]=1; } for(int t=0;t<n;t++){ for(int i=0;i<V;i++){ if(!(i>>t&1)){ s[i]=1ll*s[i]*s[i^(1<<t)]%mod; ss[i]=1ll*ss[i]*ss[i^(1<<t)]%mod; c[i]+=c[i^(1<<t)]; } } } for(int i=0;i<V;i++)ss[i]=1ll*ss[i]*KSM(s[i],(mod-2)*2)%mod; for(int i=0;i<V;i++)f[i]=1ll*s[i]*KSM(mod-2,__builtin_popcount(i))%mod; FWT(f,V,1); for(int i=0;i<V;i++)f[i]=1ll*f[i]*f[i]%mod; FWT(f,V,mod-1); int res=0; for(int i=0;i<V;i++){ Add(res,1ll*f[i]*ss[i]%mod*KSM((mod+1)/2,__builtin_popcount(i))%mod); } printf("%d\n",res); } ```