题解:P13275 [NOI2025] 集合
谨以此学习集合容斥、集合幂级数
集合容斥:
原理:
子集形式:
超集形式:
证明:
其实对子集的形式证明即可:
令 \(F(S)=\displaystyle\sum_{T\subseteq S}(-1)^{|S|-|T|}\),因此后面的和式可以替换:
对 \(F(S)\) 进行研究,考虑枚举 \(|T|=i\):
因此只有 \(P=S\) 时 \(F(S\setminus P)=1\),此时 \(\mathrm{res}=f(S)\),得证。
[NOI 2025] 集合
简要题意:
定义全集 \(U=[0,2^n)\),\(f(S)=\displaystyle\bigwedge_{i\in S}i,w(S)=\prod_{i\in S}a_i\),求下列式子的值:
解法:
\(P,Q\) 此时都是 \(2^{2^n}\) 级别的,太大不好处理,但是注意到 \(f(S)\) 是一个 \(n\) 位二进制数,\(2^n\) 级别,可以在此处做文章,重新定义 \(U=[0,n)\)。
考虑直接枚举 \(f(P)=f(Q)=S\):
由于 \(P,Q\) 交集为空,\(w(P\cup Q)=w(P)w(Q)\),变换和式:
对 \(F(S)=\displaystyle\sum_{f(P)=S}w(P)\) 容斥,考虑超集容斥:
带回原式:
考虑枚举 \(R=(P\cup Q)\) 来提出 \(\prod\) 的贡献,这样对于 \((P\cup Q)=R\) 所有贡献就是固定的 \(w(R)\),因此我们要对 \((P\cup Q)=R\) 快速计数。
考虑 \(f(P)\supseteq T_1\) 的意义,相当于限定 \(\forall i\in P,i\supseteq T_1\),用 \(C_{T_1}\) 表示 \(T_1\) 在 \(U\) 下的补集,那么 \(P\) 就是 \(C_{T_1}\) 中的数构成的。因此可以构成 \(P,Q\) 的数集就是 \((C_{T_1}\cup C_{T_2})\),因此 \(R\subseteq (C_{T_1}\cup C_{T_2})\),,然后考虑每个数 \(i\in R\) 对方案数的贡献,如果 \(i\in (C_{T_1}\cap C_{T_2})\),那么这个 \(i\) 可以放到 \(P,Q\),贡献为 \(2\),否则只能唯一放入 \(P\) 或 \(Q\),贡献为 \(1\),因此得到:
拆一下贡献,对于 \(C_{T_1}\cap C_{T_2}\) 部分枚举一个 \(R_1\),另外部分枚举 \(R_2\),得到:
还是不够优秀,注意到我们要求这样的值 \(\displaystyle\sum_{T\subseteq S}\prod_{i\in T}a_i\),考虑组合意义,相当于每个位置选有 \(a_i\) 种选择,不选有一种选择,选择方案就有 \((a_i+1)\) 种,于是总方案数,也就对应原来的式子,就是 \(\displaystyle\prod_{i\in S}(a_i+1)\),继续化简:
接着化简,\(C_{T_1}\cap C_{T_2}\) 表示不在 \(T_1\) 且不在 \(T_2\),即 \(C_{T_1\cup T_2}\)。类比可以得到 \((C_{T_1}\cup C_{T_2})\setminus (C_{T_1}\cap C_{T_2})=C_{T_1\cap T_2}\setminus C_{T_1\cup T_2}\)。注意到我们其实已经将其转化成了 \(\prod(C_{S})\) 的形式,于是定义 \(f(S)=\displaystyle\prod_{i\in C_{S}}(a_i+1),\quad g(S)=\displaystyle\prod_{i\in C_S} (2a_i+1)\),后半部分被替换成了:\(g(T_1\cup T_2)\displaystyle\frac{f_{T_1\cap T_2}}{f_{T_1\cup T_2}}\),又有并集又有交集,不好处理,考虑全部变成并集,将 \(f_{T_1\cap T_2}\) 变一下,注意到有交集容斥 \(T_1\cap T_2=T_1+T_2-(T_1\cup T_2)\),在补集意义下仍然成立,因此我们得到 \(f_{T_1\cap T_2}=\displaystyle\frac{f_{T_1}f_{T_2}}{f_{T_1\cup T_2}}\)。并且 \(2^{|T_1\cap T_2|}\) 也是交集,同样使用交集容斥,即 \(2^{|T_1|}2^{|T_2|}2^{-|T_1\cup T_2|}\),原式即:
到此已经可以做到 \(O(4^n)\),然后我们考虑令 \(F(S)=(-2)^{|S|}f_{S},\quad G(S)=\displaystyle\frac{g(S)}{2^{|S|}{(f_{S}})^2}\),并提出 \(S=T_1\cup T_2\) 拆掉贡献:
完美!此时我们发现后面的和式变成了 \(\mathrm{OR-FWT}\) 的标准形式,直接 \(O(2^n n)\) 解决。
但是注意到 \(f_S\) 处在分母上,如果 \(a_i+1=0\),那么除 \(0\) 没有良定义,答案会错,因此这样只能通过 \(B\) 性质。考虑这样一个 \(\texttt{trick}\),将每个数保存成 \(a\cdot 0^b\) 的形式,如果 \(b=0\) 值就是 \(a\),否则是 \(0\),这样乘除就有良定义了,不过在加减法的运算中就比较奇怪,如果 \(a_1\ne a_2\) 那么两个数相当于被扩域成多项式,不过由于最终只有最低此项(即 \(b=0\))的位置有用,所以直接保留 \(b\) 小的数作为结果即可。
$O(2^nn)$ B性质
void FWT(int f[],int op)
{
for (int mid=1;mid<(1<<n);mid<<=1)
{
for (int i=0;i<(1<<n);i+=(mid<<1))
{
for (int j=0;j<mid;j++)
{
adm(f[i+j+mid],Mod(f[i+j]*op));
}
}
}
}
void solve()
{
cin >> n;
for (int i=0;i<(1<<n);i++)
{
cin >> a[i];
f[i]=Mod(a[i]+1); g[i]=Mod(2ll*a[i]%mod+1);
}
for (int k=0;k<n;k++)
{
for (int i=0;i<(1<<n);i++)
{
if (!((i>>k)&1))
{
f[i]=(ll)f[i]*f[i^(1<<k)]%mod;
g[i]=(ll)g[i]*g[i^(1<<k)]%mod;
}
}
}
for (int i=0;i<(1<<n);i++)
{
F[i]=(1ll<<ppc[i])*(ppc[i]&1 ? mod-1 : 1)%mod*f[i]%mod;
int iv=qmi(f[i],mod-2);
G[i]=(ll)g[i]*iv2[ppc[i]]%mod*iv%mod*iv%mod;
}
FWT(F,1);
for (int i=0;i<(1<<n);i++) F[i]=(ll)F[i]*F[i]%mod;
FWT(F,-1);
int ans=0;
for (int i=0;i<(1<<n);i++) adm(ans,(ll)G[i]*F[i]%mod);
cout << ans << "\n";
}
$O(2^nn)$ 正解
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
const int mod=998244353;
inline int Mod(int x) { return x<0 ? x+mod : (x>=mod ? x-mod : x); }
inline void adm(int &x,int y) { x=Mod(x+y); }
inline int qmi(ll a,int b)
{
ll res=1;
for (;b>>=1;a=a*a%mod) if (b&1) res=res*a%mod;
return res;
}
struct poly
{
int x,k;
poly() { x=0,k=0; }
poly(int _x)
{
if (_x) { x=_x,k=0; }
else { x=1,k=1; }
}
poly(int _x,int _k): x(_x),k(_k) {}
friend poly operator + (const poly &lhs,const poly &rhs)
{
if (lhs.k==rhs.k) return poly(Mod(lhs.x+rhs.x),lhs.k);
else return lhs.k<rhs.k ? lhs : rhs;
}
friend poly operator - (const poly &lhs,const poly &rhs)
{
if (lhs.k==rhs.k) return poly(Mod(lhs.x-rhs.x),lhs.k);
else return lhs.k<rhs.k ? lhs : poly(Mod(-rhs.x),rhs.k);
}
friend poly operator * (const poly &lhs,const poly &rhs) { return poly((ll)lhs.x*rhs.x%mod,Mod(lhs.k+rhs.k)); }
friend poly inv(const poly &o) { return poly(qmi(o.x,mod-2),Mod(-o.k)); }
friend poly operator / (const poly &lhs,const poly &rhs) { return lhs*inv(rhs); }
};
const int N=(1<<20)|1;
int n;
int a[N];
poly f[N],g[N];
int iv2[N],ppc[N];
poly F[N],G[N];
void FWT(poly f[],int op)
{
for (int mid=1;mid<(1<<n);mid<<=1)
{
for (int i=0;i<(1<<n);i+=(mid<<1))
{
for (int j=0;j<mid;j++)
{
poly &v=f[i+j+mid];
if (op==1) v=v+f[i+j];
else v=v-f[i+j];
}
}
}
}
void solve()
{
cin >> n;
for (int i=0;i<(1<<n);i++)
{
cin >> a[i];
f[i]=poly(Mod(a[i]+1)); g[i]=poly(Mod(2ll*a[i]%mod+1));
}
for (int k=0;k<n;k++)
{
for (int i=0;i<(1<<n);i++)
{
if (!((i>>k)&1))
{
f[i]=f[i]*f[i^(1<<k)];
g[i]=g[i]*g[i^(1<<k)];
}
}
}
for (int i=0;i<(1<<n);i++)
{
F[i]=poly((1ll<<ppc[i])*(ppc[i]&1 ? mod-1 : 1)%mod)*f[i];
poly iv=inv(f[i]);
G[i]=g[i]*poly(iv2[ppc[i]])*iv*iv;
}
FWT(F,1);
for (int i=0;i<(1<<n);i++) F[i]=F[i]*F[i];
FWT(F,-1);
int ans=0;
for (int i=0;i<(1<<n);i++)
{
poly res=F[i]*G[i];
if (!res.k) adm(ans,res.x);
}
cout << ans << "\n";
}
int main()
{
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
int inv2=(mod+1)/2;
iv2[0]=1;
for (int i=1;i<N;i++) iv2[i]=(ll)iv2[i-1]*inv2%mod;
for (int i=1;i<N;i++) ppc[i]=ppc[i>>1]+(i&1);
int cid,T; cin >> cid >> T;
while (T--) solve();
return 0;
}

浙公网安备 33010602011771号