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

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

题目大意:有个大小为n的集合S,求所有选出若干非空且互不相等的子集使得交集大小是k的倍数。要求一个O(nk)的做法。
先来说说二项式反演这件事情:
P ( x ) = ∑ k = 0 x Q ( k ) ( x k ) Q ( x ) = ∑ k = 0 x P ( k ) ( x k ) ( − 1 ) x − k P(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 = 0 n P ( 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 = 0 n Q ( i ) ∑ j = 0 i ( i j ) f ( j ) \sum_{i=0}^nQ(i)\sum_{j=0}^i\binom ij f(j) i=0nQ(i)j=0i(ji)f(j)
而我们最后希望这个东西是: ∑ i = 0 n Q ( i ) [ i = = k ] \sum_{i=0}^nQ(i)[i==k] i=0nQ(i)[i==k]
也就是 ∑ i = 0 x ( x i ) f ( i ) = [ x = = k ] \sum_{i=0}^x\binom xif(i)=[x==k] i=0x(ix)f(i)=[x==k]
我们把右边看做二项式反演的Q,那么有:
f ( x ) = ∑ i = 0 x [ i = = k ] ( − 1 ) x − i ( x i ) f(x)=\sum_{i=0}^x[i==k](-1)^{x-i}\binom xi f(x)=i=0x[i==k](1)xi(ix)
也就是当x< k时 f ( x ) = 0 f(x)=0 f(x)=0否则 f ( x ) = ( − 1 ) x − k ( x k ) f(x)=(-1)^{x-k}\binom{x}{k} f(x)=(1)xk(kx)
这样就是常说的一种特殊形式的容斥。
这个题里面,显然取 P ( x ) = ( n x ) ( 2 2 n − 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 ] = 1 k ∑ i = 0 k − 1 ( w k n ) i [k|n]=\frac 1k \sum_{i=0}^{k-1}(w_k^n)^i [kn]=k1i=0k1(wkn)i
单位根的一个应用是直接把 w k i w_k^i wki这玩意带入到多项式里面,这样求和之后就只剩下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);
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值