[真学习笔记] 前夕 - 单位根反演 - 广义容斥

本文探讨了广义容斥原理的应用,并详细解释了如何利用该原理解决特定组合数学问题。同时介绍了单位根在计算组合数过程中的巧妙应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这次是真的学习笔记了……
真正意义上搞明白广义容斥实在说啥……
真正搞明白了单位根的那个性质……

题目大意:有个大小为n的集合S,求所有选出若干非空且互不相等的子集使得交集大小是k的倍数。要求一个O(nk)的做法。
先来说说二项式反演这件事情:
P(x)=∑k=0xQ(k)(xk)Q(x)=∑k=0xP(k)(xk)(−1)x−kP(x)=\sum_{k=0}^x Q(k)\binom xk\\Q(x)=\sum_{k=0}^x P(k)\binom xk (-1)^{x-k}P(x)=k=0xQ(k)(kx)Q(x)=k=0xP(k)(kx)(1)xk
这个显然是对的,因为你至少可以把互相带入……
知道了这件事情我们就可以去做一些广义容斥,例如,我们假定我们已经知道了P(k)表示大小至少k的答案,然后记Q(k)表示大小恰好k的答案,我们想通过P直接算出Q,也就是:
Q(k)=∑i=0nP(i)f(i)Q(k)=\sum_{i=0}^nP(i)f(i)Q(k)=i=0nP(i)f(i)
也就是给P加一个系数f。
而左半部分就是下式,考虑一个大小是i的集合会被P(x)f(x)计算C(x, i)f(x)次:
∑i=0nQ(i)∑j=0i(ij)f(j)\sum_{i=0}^nQ(i)\sum_{j=0}^i\binom ij f(j)i=0nQ(i)j=0i(ji)f(j)
而我们最后希望这个东西是:∑i=0nQ(i)[i==k]\sum_{i=0}^nQ(i)[i==k]i=0nQ(i)[i==k]
也就是∑i=0x(xi)f(i)=[x==k]\sum_{i=0}^x\binom xif(i)=[x==k]i=0x(ix)f(i)=[x==k]
我们把右边看做二项式反演的Q,那么有:
f(x)=∑i=0x[i==k](−1)x−i(xi)f(x)=\sum_{i=0}^x[i==k](-1)^{x-i}\binom xif(x)=i=0x[i==k](1)xi(ix)
也就是当x< k时f(x)=0f(x)=0f(x)=0否则f(x)=(−1)x−k(xk)f(x)=(-1)^{x-k}\binom{x}{k}f(x)=(1)xk(kx)
这样就是常说的一种特殊形式的容斥。
这个题里面,显然取P(x)=(nx)(22n−k−1)P(x)=\binom nx\left(2^{2^{n-k}}-1\right)P(x)=(xn)(22nk1)
然后这里的[i==k][i==k][i==k]要变成[k∣i][k|i][ki],也就是只对k的倍数那里统计。
然后就是单位根的性质:
[k∣n]=1k∑i=0k−1(wkn)i[k|n]=\frac 1k \sum_{i=0}^{k-1}(w_k^n)^i[kn]=k1i=0k1(wkn)i
单位根的一个应用是直接把wkiw_k^iwki这玩意带入到多项式里面,这样求和之后就只剩下k的倍数的项的系数的和的k倍了。
另一个就是直接展开,像这个题直接像上一个例子那样那[i==k]那里替换成[k|i]然后替换成上面单位根的式子然后推导一番即可。注意到交换求和顺序后(也就是先对不同的单位根求和再加起来)后面那一坨可以用二项式定理缩起来。
代码里面是假定k=4的题。

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#define N 10000010
#define lint long long
#define p 998244353
using namespace std;
inline int mol(lint x) { return x%=p,(x<0?x+p:x); }
int w[6],v[6],f[N],fi[N],a[N];
inline int fast_pow(int x,int k,int ans=1)
{	for(;k;k>>=1,x=(lint)x*x%p) (k&1)?ans=(lint)ans*x%p:0;return ans;	}
int main()
{
	int n;scanf("%d",&n);
	w[0]=1,w[1]=fast_pow(3,(p-1)/4),w[2]=(lint)w[1]*w[1]%p,w[3]=(lint)w[1]*w[2]%p;
	w[1]=v[1]=mol(w[1]-1),w[2]=v[2]=mol(w[2]-1),w[3]=v[3]=mol(w[3]-1),f[0]=1,a[0]=2;
	for(int i=1;i<=n;i++) f[i]=(lint)f[i-1]*i%p,a[i]=(lint)a[i-1]*a[i-1]%p;
	fi[n]=fast_pow(f[n],p-2);
	for(int i=n-1;i>=0;i--) fi[i]=fi[i+1]*(i+1ll)%p,a[i]=a[i]-1,(a[i]<0?a[i]+=p:0);
	lint ans=4ll*a[n]*fi[n]%p;
	for(int i=1;i<=n;i++)
		ans+=(1ll*w[1]+w[2]+w[3])*fi[i]%p*fi[n-i]%p*a[n-i]%p,
		w[1]=(lint)w[1]*v[1]%p,w[2]=(lint)w[2]*v[2]%p,w[3]=(lint)w[3]*v[3]%p;
	return !printf("%lld\n",(lint)mol(ans)*f[n]%p*fast_pow(4,p-2)%p);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值