容斥与反演

容斥与反演

前言:笔者写的东西大都是带有强烈主观思想的实验性笔记,并不适合 OI 食用。可以选择只读练习题。

洛谷题单:容斥与反演

容斥原理

容斥原理

设全集 Un 种不同的属性,拥有第 i 种属性的元素构成集合 Xi。显然 XiU

为了方便表述,下文将 U,Xi 类的集合称作 ”容斥集“,将构成 U 的所有元素称为 ”容斥素“。称一个容斥集 Y 具有属性 z,当且仅当 YXz

容斥原理:

|i=1nXi|=k=1n(1)k11a1<<akn|i=1kXai|

其中 || 符号表示容斥集的 带权大小(即容斥集内所有元素的权值之和)。

证明:考察每个容斥素的贡献系数是否均为 1。对于元素 x,假设其在 Y1,Y2,,Ym 中出现,则计算其贡献系数为:

cnt=k=1m(1)k1|{a1,,ak|1a1<a2<<akm}|=k=1m(1)k1(mk)=k=0m(1)k1(mk)((1)1(m0))=1

每个容斥素的贡献系数均为 1,合起来即为并集。

集合的交可以用全集减去补集的并集计算:

|i=1nXi|=|U||i=1nXi|

广义容斥原理

α(k) 表示至少满足 k 种属性的 容斥集大小之和β(k) 表示恰好满足 k​​ 种属性的 容斥素权值之和

注意 α(k) 的定义允许了容斥素被统计多次。

容斥原理通过计算 α(k) 得到容斥全集的大小,进而可以得出恰好满足 0/n 种属性的容斥素权值之和;广义容斥原理则可以对任意 1kn 求出恰好满足 k 种属性的所有容斥素权值之和。

首先有:

α(k)=1a1<<akn|Ui=1kXai|

α(k) 将恰好满足 i 种属性的容斥素计算了 (ik) 次,这相当于在 i 种属性中选 k 种作为被 钦定 的属性,即 a1,,ak。用一个简单的容斥计算 β(k)

β(k)=α(k)i=k+1n(ik)β(i)

更好的计算方式是:

β(k)=i=kn(ik)(1)ikα(i)

证明:考察是否只有恰好满足 k 种属性的容斥素有且仅有 1 份贡献,而其它容斥素贡献为 0

考察一个恰好满足 t 种属性的容斥素。

  • t<k:没有被计算过。

  • t=k:只有当 i=k 时有 1 份贡献。

  • t>k​:直接写出贡献式:

    cnt=i=kt(ik)(1)ik(ti)=i=kt(tkik)(1)ik=i=0tk(tki)(1)i=0

因此广义容斥原理成立。

k=0 的特殊情况如下:

β(0)=i=0n(1)iα(i)|i=1nXi|=|U|k=1n(1)k+11a1<<akn|i=1kXai|

这是经典容斥原理利用德摩根定律的结果。

附:德摩根定律:

i=1nXi=i=1nXii=1nXi=i=1nXi

P3813 [FJOI2017] 矩阵填数

Problem

给定一个 h×w 的矩阵,你需要在每个格子中填入 1m 中的某个数。

给这个矩阵填数的时候有一些限制,给定 n 个该矩阵的子矩阵,以及该子矩阵的最大值 v,要求你所填的方案满足该子矩阵的最大值为 v

现在,你的任务是求出有多少种填数的方案满足 n 个限制。两种方案是不一样的当且仅当两个方案至少存在一个格子上有不同的数。

由于答案可能很大,你只需要输出答案对 109+7 取模后的结果。T51h,w,m1041n101vm

Solution

记容斥全集为 U,记 Xi 表示第 i 个子矩阵最大值为 vi 的填数状态的容斥集。

定义此处 || 每个容斥素 S 附带的权值均为 1,答案即为 |i=1nXi|

进行容斥:

Ans=|U|+k=1n(1)k1a1<<akn|i=1kXai|

然而还是不好算:|原因在于 Xi 中既可能有子矩阵最大值小于 vi 的情况,也可能有子矩阵最大值大于 vi 的情况。

不妨在最开始就避免后者,即定义一个填数状态必须满足每个位置不超过在该位置的所有限制的最小值。

这样 1a1<<akn|i=1kXai| 就好算了,对每个枚举的 a 序列做 n 次矩阵求 min​​,然后求矩阵的所有格子上限乘积。这个就随便实现了。

反射容斥

之前 lh 的模拟赛中 T1 放了一道全场最难的反射容斥,直接把我创似了。

这一节与前后文关系不大,可以选择跳过。或者说是笔者还没有找到它与经典容斥的联系:|

首先,你需要知道如何用网格图推导卡特兰数的通项公式,这个你随便一搜就出来了,然后你领悟的一个重要思想,叫做 翻折终点,这就是反射容斥的 反射

然后你就会单侧反射容斥了,所以我们接下来只讨论双限制的反射容斥。

问题:给定 y=x+by=xcu,v>0),求从 (0,0) 向右或向上移动到 (n,m) 且不碰线的方案数。

我们可以用一串字符序列表示经过两条线的情况,如果多次经过一条线,等价于经过一次(都只会翻折一次),因此把连续相同字符合并,表示 “由哪条线进行翻折” 的序列。

接下来使用二元容斥。

设容斥全集为 UXi 表示 钦定 经过 bcb(长为 i)的路径的容斥集,Yi 表示 钦定 经过 cbc(长为 i)的路径的容斥集。

这里并没有使用经典容斥原理,而是具体问题具体分析,得到以下结论:

XiYi=Xi+1Yi+1

这个结论太优美了,它如此生动清晰。如果非要给它一个证明:

首先要承认事实:XiXj,YiYj,XiYj,YiXj(i<j)。于是易得:Xi+1Yi+1XiYi​。

下证 XiYiXi+1Yi+1​。取任意容斥素 aXiYi,它一定有一个长为 i 的子序列 bcb 和一个长为 i 的子序列 cbc,将两者进行拼接,一定可以得到一个长为 i+1 的子序列,不管这个子序列是以 b 开头还是以 c 开头,都一定有 aXi+1Yi+1

所以 XiYi=Xi+1Yi+1

有了这个结论就好办了。由经典容斥原理,|XiYi|=|Xi|+|Yi||XiYi|,不断进行代换可以得到答案:

Ans=|X1Y1|=|X1Y1|=|U||X1Y1|=|U||X1||Y1|+|X1Y1|=|U||X1||Y1|+|X2Y2|=|U||X1||Y1|+|X2|+|Y2||X3Y3|==|U|+k(1)k(|Xk|+|Yk|)

最后来分析一下求解上式的复杂度。

(n,m) 经过两次翻折后,会到达 (n+(b+c),m(b+c)),假设 n,m 同级,则复杂度为 O(nb+c)

笔者尝试用经典容斥直接统一反射容斥,未果,但好在得到了以上解释反射容斥原理的一个思路。

反射容斥真的太独特了,它的独特是集合反演、二项式反演等无法比及的。或许反射容斥注定与经典容斥属于不同的道路?这是笔者仍在思考的地方。

2023.11.16T1

Problem

小薰和公生制作了 n 个相同的钢琴白键,m 个相同的钢琴黑键,他们将会把 n+m 个琴键以任意的顺序拼成一个排列,组成一台「优美」钢琴。

如果一个排列的任意的一个区间 [l,r],里面的白键和黑键的数量之差的绝对值不超过 k,小薰和公生就认为这个排列制作出来的钢琴是「优美」的。

然而制作出来的琴键的原排列并不一定「优美」,小薰和公生决定对它们重新排列,我发现你愿意帮助他们,请数出他们可以组装多少种不同的「优美」的钢琴,由于答案规模较大,请输出答案对 109+7 取模的结果。1n,m107,0k109

Solution

考虑前缀和:把白键视作 1,黑键视作 1,求出每个位置的前缀和 si,则原条件等价于 smaxsmink

考虑网格图模型:初始在 (0,0),一个白键表示向上走一步,一个黑键表示向右走一步,路径上的一个坐标 (x,y) 蕴含了该位置的前缀和为 xy

要求 (xy)max(xy)mink,考虑枚举上下界:ixyi+k,得到 smaxsmink 的(存在重复计数的)路径方案数 W(k)

考虑到一个 smaxsmin=Δ 的路径实际会被计算 kΔ+1 次,因此答案可以表示为 W(k)W(k1)

假设 n,m 同级。O(k) 枚举上下界,O(nk) 计算一个上下界对应的方案数,因此复杂度为 O(n)​。

这里给出一个实现。

// Sea, You & Me
const int N = 2e7 + 5;
int fac[N], facinv[N];
void preprocess()
{
	fac[0] = facinv[0] = 1;
	for(int i = 1; i < N; ++i)
		fac[i] = mul(fac[i - 1], i);
	facinv[N - 1] = qwqmi(fac[N - 1]);
	for(int i = N - 2; i >= 1; --i)
		facinv[i] = mul(facinv[i + 1], i + 1);
}
int C(int n, int m)
{
	if(n < 0 || m < 0 || n < m) return 0;
	return mul(fac[n], mul(facinv[m], facinv[n - m]));
}
int f(int n, int m)
{
	if(n < 0 || m < 0) return 0;
	return C(n + m, n);
}
PII sym(PII p, int b)
{
	return MP(p.se - b, p.fi + b);
}
int dfs(PII p, int a, int b)
{
	if(p.fi < 0 || p.se < 0) return 0;
	return dec(f(p.fi, p.se), dfs(sym(p, a), b, a));
}
int calc(int n, int m, int k)
{
	int res = 0;
	for(int i = 0; i >= -k; --i)
	{
		if(n + i > m || n + i + k < m) continue;
		Inc(res, f(n, m));
		Dec(res, dfs(sym(MP(n, m), i - 1), i + k + 1, i - 1));
		Dec(res, dfs(sym(MP(n, m), i + k + 1), i - 1, i + k + 1));
	}
	return res;
}
int n, m, k;
int main()
{
	preprocess();
	read(n), read(m), read(k);
	if(abs(n - m) > k || k == 0) return puts("0"), 0;
	if(k >= max(n, m)) return printf("%d\n", f(n, m)), 0;
	printf("%d\n", dec(calc(n, m, k), calc(n, m, k - 1)));
	return 0;
}

/* sample 1
2 2 1
ans : 2
*/

/* sample 2
19 12 2
ans : 0
*/

/* sample 3
11 14 4
ans : 485072
*/

/* sample 4
133 128 20
ans : 170238429
*/

/* sample 5
11451 11450 2
ans : 199761117
*/

/* sample 6
1926081 9999999 9982443
ans : 785189997
*/

P3266 [JLOI2015] 骗我呢

Problem

求满足以下限制的 nm 列的矩阵的数量对 109+7 取模的结果。

  • 0xi,jm
  • 1in,1j<m,有 xi,j<xi,j+1
  • 1<in,1j<m,有 xi,j<xi1,j+1

1n,m106

Solution

一个很好的性质是:每行只会有一个数值不会出现。

因此可以设计 fi,j 表示考虑前 i 行,其中第 i 行数 j 没有出现的方案数。转移为:

fi,j=k=0j+1fi1,k

把和式消掉:

fi,j1=k=0jfi1,k=fi,jfi1,j+1fi,j=fi,j1+fi1,j+1

注意当 j=0 时,fi,j1 没有定义,要特殊转移:

fi,0=fi1,0+fi1,1

初始化:f1,0=f1,1==f1,m=1

我们对 fn+1, 进行同样的转移,答案为 j=0mfn,j=fn+1,m​。

接下来的数形结合厉害了!

下面是一个 n=4,m=3 的例子。

做完了。

集合反演

f(S)=TSg(T)g(S)=TS(1)|S||T|f(T)f(S)=STg(T)g(S)=ST(1)|T||S|f(T)

这里对第一个式子给出证明。

充分性:

g(S)=TS(1)|S||T|QTg(Q)=Qg(Q)QTS(1)|S||T|=Qg(Q)TS/Q(1)|S/Q||T|=Qg(Q)i=0|S/Q|(1)|S/Q|i(|S/Q|i)

特判当 Q=S 时(因为二项式定理后 00 无定义),i=0|S/Q|(1)|S/Q|i(|S/Q|i)=1

否则当 QS 时,i=0|S/Q|(1)|S/Q|i(|S/Q|i)=(1+1)|S/Q|=0

必要性:

f(S)=TSQT(1)|T||Q|f(Q)=Qf(Q)QTS(1)|T||Q|=Qf(Q)TS/Q(1)|T|

然后施以同样的分析,只有当 Q=S 时右式有贡献。

第二个式子推法相同,不再赘述。

对于容斥问题的意义:第一个式子给出了 至少恰好 的转换,第二个式子给出了 至多恰好 的转换。

需要指出,我只是证明了集合反演的正确性,这是非常 naive 的。等到笔者有能力明白集合反演更本质的东西时,会补充它的来由。

至于 OI-wiki 把它称作 ”容斥原理的一般化“,笔者实在是无法理解,甚至认为应该叫做 ”特殊化“ 才对。


P3349 [ZJOI2016] 小星星

Problem

给出一张图 G 和一棵树 T,点数都为 n,求有多少个长为 n 的排列 {ai},满足若存在 (u,v)ET,则 (au,av)EGn17mn(n1)2

Solution

首先有一个显然的状压 DP:记 fi,x,S 表示考虑 i 子树内的权值集合为 Sai=x 的方案数,时间复杂度 O(n33n),其中 3n 为枚举子树集合。

把集合的限制提出来做 2n 枚举。

W(S) 表示 至多 使用权值集合 S 对树进行染色并使得 “若存在 (u,v)ET(au,av)EG” 的方案数。

w(S) 表示 恰好 使用权值集合 S 对树进行染色并使得 “若存在 (u,v)ET(au,av)EG” 的方案数。

显然有 W(S)=TSw(T)

集合 S 的贡献系数显然为 [S=U]。记 f(S)=[S=U]g(S)S 的容斥系数,则:

f(S)=[S=U]=STg(T)g(S)=ST(1)|T||S|f(T)=(1)n|S|

答案为:

Ans=w(U)=SU(1)n|S|W(S)

笔者憨了,因为显然可以直接进行 w,W​ 的反演:|

但这也指出了一种运用集合反演的视角,即通过 进行贡献系数与容斥系数的转换 解决问题。

集合反演与容斥的关系

在本例中,我们令容斥全集为 UXXi 表示包含权值 i 的权值集合的容斥集,U​ 为权值全集。

定义此处 || 每个容斥素 S 附带的权值是 w(S)。则该题所求即为 w(U)=|i=1nXi|,表示每种权值都需出现一次。

我们用经典容斥式进行推导:

Ans=|i=1nXi|=|UX||i=1nXi|=|UX|+k=1n(1)k1a1<<akn|i=1kXai|=W(U)+k=1n(1)k1a1<<akna1S,,akSw(S)=W(U)+k=1n(1)kS,|S|=nkW(S)=W(U)+SU(1)n|S|W(S)=S(1)n|S|W(S)

我们得到了相同的结果。

容斥问题中经常会出现 至多至少恰好 的转换,在容斥原理上结合具体情形可以直观地看到转换过程,但其缺点在于过程相对繁琐,而且初学时容易混淆集合的带权问题。

而集合反演指出了一类关于集合容斥的形式化解决方法,使得过程更为简单。

这里 “集合容斥“ 的意义其实就是 把集合作为容斥素,把集合内的元素作为容斥属性

反演在容斥中体现在凑和式从 w 得到 W 的过程。

gossip:也许一年前的我即使没有系统地学这些东西也能说出一个「显然」的容斥做法,而我现在明白了这种做法的「本质」。可我又到哪里寻求「显然」的「本质」呢?

[AGC005D] ~K Perm Counting

Problem

如果一个排列 P 满足对于所有的 i 都有 |Pii|k,则称排列 P 为合法的。现给出 nk,求有多少种合法的排列。

输出答案对 924844033 取模的结果。2n2×1031kn1

Solution

上例展现的是子集反演,此例展现了超集反演。

W(S) 表示 |Pii|=k 的位置构成的集合 包含 S 的方案数(钦定 S 集合的每个位置 |Pii|=k)。

w(S) 表示 |Pii|=k 的位置构成的集合 恰好S 的方案数。

显然有 W(S)=STw(T)。反演后可知答案为:

Ans=w()=S(1)|S|W(S)

注意到对于大小均为 xSW(S) 都能表示成 (nx)!×W(S) 的形式,因为除开钦定的 x 个元素外,其它的元素可以任意排列。

因此可以记 f(i)=S|S|=iW(S),表示对大小为 i 的集合统计只考察钦定的 i​​ 个元素的排列方案数的总和。

Ans=x=0n(1)x(nx)!f(x)

f(i) 可以不同的视角用高效的方法求出,这里引入图论建模。

p1,,pn 视作左部点,1,,n 作为右部点,把 |pxy|=k 的点进行连边。下面是一个 n=9,k=2 的例子:

该图的一个匹配与钦定的排列形成双射!边数为 x 的匹配的方案数即为 f(x)。并且不难料到形成了若干条链,而这些链互不影响。

一种 naive 的思路是,对每条链做一个 DP,然后再进行背包合并。事实上,可以直接将所有链合并成一条链做一遍 DP 即可,不过要特殊判断不能选一条链的终点和另一条链的起点相连的边。时间复杂度 O(n2),已经足以通过此题。

事实上,一条长为 n 的链上选取 m 条不相邻匹配边的方案数是 (nmm),因为把一条边缩成一个点后就变成了组合问题,容易证明变换后的方案与变换前的方案一一对应。

另一个观察是:只有两种长度不同的链,因此可以直接启动多项式快速幂。

Bonus:用二项式反演推导本题,并思考集合反演与二项式反演的联系与区别。

P5644 [PKUWC2018] 猎人杀

Problem

一开始有 n 个猎人,第 i 个猎人有仇恨度 ai ,每个猎人只有一个固定的技能:死亡后必须开一枪,且被射中的人也会死亡。

然而向谁开枪也是有讲究的,假设当前还活着的猎人有 [i1im],那么有 aikj=1maij 的概率是向猎人 ik 开枪。

一开始第一枪由你打响,目标的选择方法和猎人一样(即有 aij=1naj 的概率射中第 i 个猎人)。

由于开枪导致的连锁反应,所有猎人最终都会死亡,现在 1​ 号猎人想知道它是最后一个死的的概率。

答案对 998244353 取模。ai>0,且 1i=1nai100000

Solution

记全集为 UAS=iSai

首先,由于猎人似掉的概率时刻在变化,这不方便我们分析,因此要对问题进行转化。

原问题等价于:可以向尸体开枪,则猎人 i 在一次开枪中似掉的概率为 aiAU

证明:假设当前活着的人集合为 S,对于一个活着的人 i,其在原问题中被击杀的概率为 aiAS

在新的模型中,其被击杀的概率为 t=0(AUASAU)t×aiAU=11AUASAU×aiAU=aiAS。因此两者等价。

w(S) 表示 1 之后似的人的集合 恰好S 的概率。

W(S) 表示 1 之后似的人的集合 包含 S​ 的概率。

注意 1S

显然有 W(S)=STw(T),这是超集反演的形式,得到:

Ans=w()=S(1)|S|W(S)

考虑 W(S) 是什么:相当于不断向 S{1} 内打枪,直到打中 1 的概率(我记得这个叫几何分布)。

W(S)=i=0(AUASa1AU)ia1AU=11AUASa1AU×a1AU=a1AS+a1

从集合的角度很难继续考虑问题了,注意到 AU105,经常使用生成函数的朋友们都知道:

Ans=s=0a1s+a1[xs]i=2n(1xai)

这是喜闻乐见的分治 + NTT,时间复杂度两只老哥。

P5405 [CTS2019] 氪金手游

Problem

n 种卡牌,每一轮抽到第 i 种卡牌的概率为 aij=1najaipi,1 的概率取到 1,有 pi,2 的概率取到 2,有 pi,3 的概率取到 3pi,1+pi,2+pi,3=1

设第一次抽到 i 的时间为 Ti,有 n1 对限制构成一棵有向树,满足对于所有树边 uiviTui<Tvi。求满足所有限制的概率。n1000

Solution

首先考虑外向树:记 Au 表示 u 子树内的 a 权值之和,sum 表示所有点的 a 权值之和,则 u 是其子树内第一个被抽到的概率为:

ausumi=0(sumAusum)i=auAu

于是可以记 dpu,i 表示考虑 u 子树内,Au=i 且满足所有限制的概率,答案为 idproot,i

对于存在反向边的情况,考虑用 无限制 减去 正向边(将反向边反向) 进行计算

记反向边集合为全集 U

w(S) 表示被反向的边集 恰好S 的概率。

W(S) 表示被反向的边集 包含 S 的概率。

W(S)=STw(T)w(S)=ST(1)|T||S|W(T)。于是:

Ans=w()=S(1)|S|W(S)

思考 W(S) 的实际意义,相当于 钦定S 内的边进行反向,S 内的边的限制进行消除,而这可以带上前面的容斥系数用 DP 解决。

具体地,记 dpu,i 表示考虑 u 子树内,Au=i(注意考虑 S 内的边,但不考虑 S​ 内的边)的带容斥系数的概率和。

初始化:dpu,1=pu,1dpu,2=2pu,2dpu,3=3pu,3。由于 Au 未知,所以要留到最后处理。

考虑 u 的子节点 vu​ 的转移。

若限制为 uv

dpu,i+jtmpi×dpv,j

若限制为 vu

dpu,i+jtmpi×dpv,jdpu,itmpi×dpv,j

遍历完 u 的所有子节点后:

dpu,i×1i

启发:连续四道(包括下面这道神题)都是相同的超集反演的形式。在答案只需求空集权值的情况下,反演对象可以设计得非常大胆,而这也是最值得思考的一部分。

P9563 [SDCPC2023] Be Careful 2

Problem

小青鱼有一个位于二维平面上的,大小为 n×m 的矩形。矩形的右上角位于 (n,m),而左下角位于 (0,0)

矩形内部有 k 个禁止点,第 i 个禁止点位于 (xi,yi)。保证所有禁止点互不相同。

请计算小青鱼可以画的正方形的总面积对 998244353 取模的结果。2n,m1091k5×103​。

Solution

有眼不识泰山,我把这题想得太简单了。

w(S) 表示点集 恰好S 的权值和。

W(S) 表示点集 包含 S 的权值和。

W(S)=STw(T)w(S)=ST(1)|T||S|W(T)

所以答案为 Ans=w()=S(1)|S|W(S)

枚举 S 是不现实的,DP 貌似也不太可行,这里选择 改变枚举对象,把枚举级别降到多项式复杂度

想知道包含点集 S 的权值和,其实只需要知道包含 S最小矩形,而矩形的数量是 O(k4)​​ 的,这样就优化了不少。

为了方便表述,我们称去除实际矩形的最外层后留下的图案称为简化矩形(这个图案可能不是矩形)。

f(A) 表示包含简化矩形 A 的所有正方形的面积和,则:

Ans=tot+Af(A)包含 S 的最小矩形为 A(1)|S|

思考后面那坨容斥系数之和如何计算时,发现还可以继续优化:如果一个点不在简化矩形边界上,该矩形贡献为 0

也就是说,只有所有点都在边界上的简化矩形是有贡献的。考虑枚举左右边界上的点,则上下边界各 2 种可能(自己的纵坐标作为上/下边界,或前驱/后继的纵坐标作为上/下边界),因此两个点之间最多产生 4 个有效矩形,矩形数量级为 O(k2)​​。

for(int i = 1; i <= K; ++i)
{
    int U = INF, D = -INF;
    a[++cnt] = Rec(p[i].fi, p[i].fi, p[i].se, p[i].se);
    for(int j = i + 1; j <= K; ++j)
    {
        if(p[j].se >= U || p[j].se <= D) continue;
        int mn = min(p[i].se, p[j].se);
        int mx = max(p[i].se, p[j].se);
        a[++cnt] = Rec(p[i].fi, p[j].fi, mx, mn);
        a[++cnt] = Rec(p[i].fi, p[j].fi, mx, D);
        a[++cnt] = Rec(p[i].fi, p[j].fi, U, mn);
        a[++cnt] = Rec(p[i].fi, p[j].fi, U, D);
        if(p[j].se >= p[i].se) U = p[j].se;
        if(p[j].se <= p[i].se) D = p[j].se;
    }
}
sort(a + 1, a + cnt + 1);
cnt = unique(a + 1, a + cnt + 1) - (a + 1);

枚举出所有简化矩形后,分类讨论不同形态的贡献系数:

  • 一个点:1

  • 一条线:

    • 仅两个端点上存在禁止点:1
    • otherwise0
  • 是一个正规矩形:

    以下称边上的禁止点时,不包含角上的禁止点。

    • 四个角上没有禁止点:1
    • 一个角上有禁止点:
      • 不选这个角:若每条边上有禁止点,贡献 1,否则为 0
      • 选这个角:若角的相邻两边有禁止点,贡献 0,否则为 1
    • 两个相邻角上有禁止点:
      • 不选角:同上。
      • 选一个角:若与被选角相邻的两边上有禁止点,贡献为 0;若另外两边上没有禁止点,贡献为 0;否则为 1​。
      • 选两个角:若与角相邻的三边上有禁止点,贡献为 0;否则为 1​。
    • 两个对角上有禁止点:
      • 不选角:同上。
      • 选一个角:同上。
      • 选两个角:若存在一边有禁止点,贡献为 0,否则为 1
    • 三个角上有禁止点:
      • 不选角:同上。
      • 选一个角:同上。
      • 选两个邻角:若与被选角相邻的三边上有禁止点,贡献为 0;若另外一边上没有禁止点,贡献为 0;否则为 1
      • 选两个对角:若存在一边有禁止点,贡献为 0,否则为 1
      • 选三个角:若存在一边有禁止点,贡献为 0,否则为 1
    • 四个角均有禁止点:
      • 不选角:同上。
      • 选一个角:同上。
      • 选两个邻角:同上。
      • 选两个对角:同上。
      • 选三个角:同上。
      • 选四个角:若存在一边有禁止点,贡献为 0,否则为 1

以上均可用二项式定理推出。

这太难绷了!而且实现会带上一只老哥。笔者尝试实现,发现其在极端数据下要跑将近 20 秒,还有空间爆炸的危险,无法通过。

int calc_coef(int l, int r, int u, int d)
{
	PII w = MP(l, d), x = MP(l, u), y = MP(r, u), z = MP(r, d);
	int vw = mp[w], vx = mp[x], vy = mp[y], vz = mp[z];
	int v1 = 0, v2 = 0, v3 = 0, v4 = 0;
	SII it1 = Sx[mpx[l]].upper_bound(d); if(it1 != Sx[mpx[l]].end() && *it1 < u) v1 = 1;
	SII it2 = Sy[mpy[u]].upper_bound(l); if(it2 != Sy[mpy[u]].end() && *it2 < r) v2 = 1;
	SII it3 = Sx[mpx[r]].upper_bound(d); if(it3 != Sx[mpx[r]].end() && *it3 < u) v3 = 1;
	SII it4 = Sy[mpy[d]].upper_bound(l); if(it4 != Sy[mpy[d]].end() && *it4 < r) v4 = 1;
	if(l == r && u == d) return -1;
	if(l == r) return (v1 == 0 ? 1 : 0);
	if(u == d) return (v2 == 0 ? 1 : 0);
	int cnt = vw + vx + vy + vz;
	int emp = (v1 == 0 && v2 == 0 && v3 == 0 && v4 == 0);
	// choose 0
	int res = (v1 == 1 && v2 == 1 && v3 == 1 && v4 == 1); 
	if(cnt == 0) return res;
	// choose 1
	if(vw == 1) res -= (v1 == 0 && v4 == 0 && v2 == 1 && v3 == 1);
	if(vx == 1) res -= (v1 == 0 && v2 == 0 && v3 == 1 && v4 == 1);
	if(vy == 1) res -= (v2 == 0 && v3 == 0 && v1 == 1 && v4 == 1);
	if(vz == 1) res -= (v3 == 0 && v4 == 0 && v1 == 1 && v2 == 1);
	if(cnt == 1) return res;
	// choose 2
	if(vw == 1 && vx == 1) res -= (v1 == 0 && v2 == 0 && v4 == 0 && v3 == 1);
	if(vx == 1 && vy == 1) res -= (v1 == 0 && v2 == 0 && v3 == 0 && v4 == 1);
	if(vy == 1 && vz == 1) res -= (v2 == 0 && v3 == 0 && v4 == 0 && v1 == 1);
	if(vz == 1 && vw == 1) res -= (v1 == 0 && v3 == 0 && v4 == 0 && v2 == 1);
	if(vw == 1 && vy == 1) res += emp;
	if(vx == 1 && vz == 1) res += emp;
	if(cnt == 2) return res;
	// choose 3
	if(vw == 1 && vx == 1 && vy == 1) res -= emp;
	if(vx == 1 && vy == 1 && vz == 1) res -= emp;
	if(vy == 1 && vz == 1 && vw == 1) res -= emp;
	if(vz == 1 && vw == 1 && vx == 1) res -= emp;
	if(cnt == 3) return res;
	if(vw == 1 && vx == 1 && vy == 1 && vz == 1) res += emp;
	return res;
}

能否在枚举矩形时直接把答案求出来呢?

我们先考虑没有横纵坐标相同的情况。记 l,r,u,d 分别为四个边界的值,hu,hd 分别为 u 之上的最小高度和 d 之下的最大高度,(x1,y1) 为枚举的左边界,(x2,y2) 为枚举的右边界,如果 y2hdy2hu 则跳过(这里的等号是针对于下文的普遍情况)。

  • u=max(y1,y2)d=min(y1,y2):对应矩形 +1 贡献。
  • u=hud=min(y1,y2):对应矩形 1 贡献。
  • u=max(y1,y2)d=hd:对应矩形 1 贡献。
  • u=hud=hd:对应矩形 +1 贡献。

对于有横纵坐标相同的情况,将所有点按横坐标为第一关键字从小到大排序,按纵坐标为第二关键字从小到大排序,执行以上算法,得到的答案仍然正确。

别看上面这句话轻描淡写——它对于解决本题实在是具有伟大的意义,它意味着可以将上面大量分类讨论归于一个简洁的算法,而不重不漏。

for(int i = 1; i <= K; ++i)
	{
		int U = INF, D = -INF;
		Dec(ans, calc_val(p[i].fi, p[i].fi, p[i].se, p[i].se));
		for(int j = i + 1; j <= K; ++j)
		{
			if(p[j].se >= U || p[j].se <= D) continue;
			int mn = min(p[i].se, p[j].se);
			int mx = max(p[i].se, p[j].se);
			Inc(ans, calc_val(p[i].fi, p[j].fi, mx, mn));
			Dec(ans, calc_val(p[i].fi, p[j].fi, mx, D));
			Dec(ans, calc_val(p[i].fi, p[j].fi, U, mn));
			Inc(ans, calc_val(p[i].fi, p[j].fi, U, D));
			if(p[j].se >= p[i].se) U = p[j].se;
			if(p[j].se <= p[i].se) D = p[j].se;
		}
	}

于是现在的问题是如何快速计算 fA

对于一个 (xl,yl)(xr,yr) 的实际矩形,可以如下计算:

fA=dwx(d)×wy(d)×d2

此处:

wx(d)=max(0,min(x0,nd)max(0,x1d)+1)wy(d)=max(0,min(y0,nd)max(0,y1d)+1)

因此 fA 是关于 d 的分段函数,段数为 O(1)​,可以暴力算。

const int inv6 = qwqmi(6);
const int inv4 = qwqmi(4);
const int inv30 = qwqmi(30);
int sum2(int x) { return mul(inv6, mul(x, mul(x + 1, 2 * x + 1))); } 
int sum3(int x) { return mul(inv4, mul(sqr(x), sqr(x + 1))); } 
int sum4(int x) { return mul(inv30, mul(x, mul(x + 1, mul(2 * x + 1, dec(mul(3, mul(x, x + 1)), 1))))); }
int cx[2], cy[2]; // w(len) = c[0] + c[1] * len
int func(int l, int r)
{
	l = max(0, l - 1);
	int c2 = mul(cx[0], cy[0]);
	int c3 = inc(mul(cx[0], cy[1]), mul(cx[1], cy[0]));
	int c4 = mul(cx[1], cy[1]);
	int res = 0;
	Inc(res, mul(c2, dec(sum2(r), sum2(l))));
	Inc(res, mul(c3, dec(sum3(r), sum3(l))));
	Inc(res, mul(c4, dec(sum4(r), sum4(l))));
	return res;
};
int calc_val(int l, int r, int u, int d)
{
	--l, ++r, --d, ++u;
	if(l < 0 || d < 0 || r > n || u > m) return 0;
	int mn = max(r - l, u - d), mx = min(n, m);
	vector<int> vec = {mn, mx + 1, r + 1, u + 1, n - l + 1, m - d + 1};
	sort(vec.begin(), vec.end());
	auto coef = [&](int len, int l, int r, int lim, int *c)
	{
		if(len <= lim - l && len <= r) c[0] = l - r + 1, c[1] = 1;
		else if(len <= lim - l && len > r) c[0] = l + 1, c[1] = 0;
		else if(len > lim - l && len <= r) c[0] = lim - r + 1, c[1] = 0;
		else c[0] = lim + 1, c[1] = MOD - 1;
	};
	int res = 0;
	for(int i = 0; i + 1 < (int)vec.size(); ++i)
	{
		if(vec[i] < mn) continue;
		if(vec[i] > mx) break;
		if(vec[i] == vec[i + 1]) continue;
		coef(vec[i], l, r, n, cx);
		coef(vec[i], d, u, m, cy);
		Inc(res, func(vec[i], vec[i + 1] - 1));
	}
	return res;
}

总时间复杂度 O(k2)

P8329 [ZJOI2022] 树

Problem

对于参数 n,按如下方式生成两棵均有 n 个节点的树。

第一棵树的生成方式是:节点 1 作为树的根;对于 i[2,n],从 [1,i1] 中选取一个节点作为 i 的父亲。

第二棵树的生成方式是:节点 n 作为树的根;对于 i[1,n1],从 [i+1,n] 中选取一个节点作为 i 的父亲。

要求对于任意 i[1,n],若第一棵树中的节点 i 为叶子,那么第二棵树中的节点 i 为非叶子;若第一棵树中的节点 i 为非叶子,那么第二棵树中的节点 i 为叶子。

对所有 n[2,N] 求生成两棵树的方案数是多少。两种方案不同当且仅当存在一棵树中的一个节点 i,两种方案中它的父亲不同。

输出答案对 M​ 取模后的结果。2N500,不保证 M 为质数。

Solution

f(S) 表示第一棵树的非叶节点集合 恰好S 的方案数,g(S) 表示第二棵树的非叶节点集合 恰好S​ 的方案数。

f(S) 表示第一棵树的非叶节点集合 包含于 S 的方案数,g(S) 表示第二棵树的非叶节点集合 包含于 S​​ 的方案数。

对二者分别做子集反演可以得到:

f(S)=SS(1)|S||S|f(S)g(S)=SS(1)|S||S|g(S)

则:

Ans(n)=ST=U,ST=f(S)g(T)=ST=U,ST=SSTT(1)|S||S|+|T||T|f(S)g(T)=ST=(1)(|S|+|T|)f(S)g(T)ST=USSTT(1)|S|+|T|=ST=(1)n(|S|+|T|)f(S)g(T)2n(|S|+|T|)=ST=(2)n(|S|+|T|)f(S)g(T)

其中 ST=USSTT1 转成 2n|S||T| 是在枚举 ST 中的元素属于 S 集合还是属于 T​ 集合。

后面的部分是神仙 DP,笔者认为是更困难的一部分。

dpi,j,k 表示考虑点 1,,i,其中第一棵树上 (1,i] 的父亲已定,第二棵树上 [1,i) 的父亲已定,且 |S{1,,,i}|=j,|T{i+1,,n}|=k 的带权(即容斥系数)方案数。

边界:k[1,N1],dp1,1,k=1,注意节点 1​ 一定是第一棵树的根节点、第二棵树的叶节点。初始时就已经决定了 T 的大小,之后不断地往里面添加元素。而 S 初始时只有 1,是在不断往里面加元素的过程中扩充 S 的大小。

转移:(在第一棵树上给 i 找父亲,在第二棵树上给 i1 找父亲)

  • iSdpi,j+1,kj×k×dpi1,j,k

  • iTdpi,j,k1j×k×dpi1,j,k

  • iSiTdpi,j,k2×j×k×dpi,j,k

这个过程就是:不断地将某些点纳入 S,T 点集,但是不一定要在它下面接子节点,这对应了 f,g 作为子集和的意义。

对于参数 n​,对应的答案为:

Ans(n)=dpn1,j,1×j

也许先对每个 n 单独考虑 DP 会想得通一些,然后发现统一初始化之后一遍 DP 就可以将 N1​​ 个答案一起求出。

彩蛋:QOJ# 4836. Tree。这题后来被搬到 ACM 里面去了:|

二项式反演

引子

这部分帮你快速了解二项式反演是什么以及它为什么是对的,但并不能解释更深入的东西。

f(n)=i=1n(ni)g(i)g(n)=i=1n(ni)(1)nif(i)f(n)=i=nm(in)g(i)g(n)=i=nm(in)(1)inf(i)

我们仍然使用代入法进行证明。这里只考察第一个式子。

充分性:

g(n)=i=1n(ni)(1)nij=1i(ij)g(j)=j=1ng(j)i=jn(1)ni(ni)(ij)=j=1ng(j)(nj)i=jn(1)ni(njij)=j=1ng(j)(nj)i=0nj(1)nij(nji)=j=1ng(j)(nj)(1)ji=0nj(1)ni(nji)

当且仅当 j=n 时有贡献,原因同集合反演的证明。

必要性:读者自证。(太 naive,不想写了

第一个式子给出了 钦定至多恰好 的转换,第二个式子给出了 钦定至少恰好 的转换。


从矩阵的角度推导二项式反演:反演公式的一般性

矩阵求逆。


从集合反演的角度推导二项式反演

同时作为 [AGC005D] ~K Perm Counting 一题 Bonus 的回答。

已知:

f(S)=TSg(T)g(S)=TS(1)|S||T|f(T)

f(S),g(S) 只与 |S| 有关时,用 f(|S|),g(|S|) 代替 f(S),g(S),得到:

f(n)=k=0n(nk)g(k)g(n)=k=0n(1)nk(nk)f(k)

已知:

f(S)=STg(T)g(S)=ST(1)|T||S|f(T)

同理进行替换:

f(n)=k=nm(kn)g(k)g(n)=k=nm(1)kn(kn)f(k)

即:钦定至多型二项式反演是子集反演的特殊形式,钦定至多型二项式反演是超集反演的特殊形式

由于集合反演的本质是容斥,因此我们最终还是会分析二项式反演与容斥的直接联系。


从容斥的角度推导二项式反演

这是最隐晦、最有意思的一部分,也是笔者仍未明白的一部分。

由广义容斥原理可以得到 钦定至少 型的二项式反演公式:

f(n)=k=nm(kn)g(k)g(n)=k=nm(kn)(1)knf(k)

钦定至多 型的二项式反演可以由容斥的特殊情况推出:k 个(同类)容斥集之交的(带权)大小仅与 k 有关。

f(k)=1a1<<akn|Ui=1kXai|g(k)=1a1<<akn|Ui=1kXai|

由:

|i=1nXi|=|U|k=1n(1)k11a1<<akn|i=1kXai|

与:

|i=1nXi|=|U|k=1n(1)k11a1<<akn|i=1kXai|

可得:

f(n)=k=0n(nk)(1)kg(k)g(n)=k=0n(nk)(1)kf(k)

g(k)(1)kg(k),然后我们得到:

f(n)=k=0n(nk)g(k)g(n)=k=0n(nk)(1)nkf(k)

如何在容斥意义上统一二项式反演、钦定至多型二项式反演是否有普适性应用等问题是笔者仍在思考的事情。

错排问题

Problem

i=1n[pii]=n 的长为 n 的排列数。

Solution

f(k) 表示 钦定至多 k 个位置错位的方案数,g(k) 表示 恰好 长为 k 的错排数。显然有:

f(k)=k!

枚举 恰好 k 个位置可能为 pii 以计算 f

f(n)=k=1n(nk)g(k)

反演:

g(n)=k=1n(nk)(1)nkk!

钦定至多型二项式反演与容斥的关系

在本例中,我们令容斥全集为 UXXi 表示第 i 个位置 pii 的排列的容斥集。

定义此处 || 每个容斥素 P={p1,p2,,pn} 附带的权值为 1。该题所求即为 |i=1nXi|,表示每个位置均需错位。

Ans=|i=1nXi|=|UX||i=1nXi|=|UX|+k=1n(1)k1a1<<akn|i=1kXai|=|UX|+k=1n(1)k1a1<<aknP,pa1=a1,,pak=ak1=n!+k=1n(1)k1a1<<akn(nk)!=n!+k=1n(1)k(nk)(nk)!=k=1n(nk)(1)nkk!

注意到:对于同一个 k|i=1kXai|=(nk)!,这就是 ”k 个(同类)容斥集之交的(带权)大小仅与 k 有关“。

染色问题

Problem 1

m 个格子排成一排,现有 n 种颜色,每个格子都要染上一种颜色,且要求相邻格子颜色不同,每种颜色至少出现一次,求方案数。

Solution 1

f(k) 表示 钦定至多k 种颜色进行染色的方案数,g(k) 表示 恰好k 种颜色进行染色的方案数。容易得:

f(k)=k×(k1)m1

枚举 恰好 有多少种颜色被使用以计算 f

f(n)=k=1n(nk)g(k)

反演:

g(n)=k=1n(nk)(1)nkf(k)

Problem 2

n×m 个格子,1 种颜色,求每行每列至少有一个格子被染色的方案数。

Solution

此例讲述多元情况下二项式反演与容斥的关系。

f(i,j) 表示 钦定至多ij 列的方案数,g(i,j) 表示 恰好ij 列的方案数。显然有:

f(i,j)=2ij

枚举 恰好 有有多少行/列被染色以计算 f

f(n,m)=i=0n(ni)j=0m(mj)g(i,j)

h(n,m)=j=0m(mj)g(n,j),则又有 f(n,m)=i=0n(ni)h(i,m)

f,h 做第一维的反演:

h(n,m)=i=0n(ni)(1)nif(i,m)

g,h 做第二维的反演:

g(n,m)=j=0m(mj)(1)mjh(n,j)

得到 g(n,m)

g(n,m)=j=0m(mj)(1)mji=0n(ni)(1)nif(i,j)=i=0nj=0m(ni)(mj)(1)(ni)+(mj)f(i,j)

多元钦定至多型二项式反演与容斥的关系

在本例中,定义一个染色状态 Sn×m 的二进制数,并定义 S(i,j)=[if (i,j) be colored]

UX,Y 表示容斥全集,Xi 表示第 i 行至少一个格子被染色的染色状态的容斥集,Yj 表示第 j 列至少一个格子被染色的染色状态的容斥集。

定义此处每个容斥素 S(一个 n×m 的二进制状态)附带的权值为 1。该题所求即为 |i=1nXij=1mYj|

Ans=|UX,Y||i=1nXij=1mYj|=|UX,Y||i=1nXi||j=1mYj|x=1ny=1m(1)x+y1a1<<axn1b1<<bym|i=1xXaii=1yYbi|=2nmx=1n(1)x1a1<<axn2(nx)my=1m(1)y1b1<<bym2n(my)x=1ny=1m(1)x+y1a1<<axn1b1<<bym2(nx)(my)=2nmx=1n(1)x(nx)2(nx)my=1m(1)y(my)2n(my)x=1ny=1m(1)x+y(nx)(my)2(nx)(my)=x=0ny=0m(1)x+y2(nx)(my)

注意到:对于同一个二元组 (x,y)|i=1xXaii=1yYbi|=2(nx)(my)​, 这是(钦定至多型)二元二项式反演的前提条件(存疑)。以及解释了为何在介绍一元二项式反演时备注了 ”同类“ 这一条件。

Problem 3

n×m 个格子,c 种颜色,求每行每列至少有一个格子被染色、每种颜色至少出现一次的方案数。

Solution 3

f(i,j,k) 表示 钦定至多k 种颜色染 ij 列的方案数,g(i,j,k) 表示恰好用 k 种颜色染 ij 列的方案数。显然有:

f(i,j,k)=(k+1)ij

枚举 恰好k 种颜色染 ij 列以计算 f

f(n,m,c)=i=1nj=1mk=1c(ni)(mj)(ck)g(i,j,k)

反演:

g(n,m,c)=i=1nj=1mk=1c(ni)(mj)(ck)(1)(ni)+(mj)+(ck)f(i,j,k)

BZOJ2839 集合计数

Problem

一个有 n 个元素的集合有 2n 个不同子集(包含空集),现在要在这 2n 个集合中取出若干集合(至少一个),使得它们的交集的元素个数为 m,求取法的方案数,答案模 109+71mn106

Solution

此例展示钦定至少型二项式反演在计数中的应用。

f(k) 表示 钦定至少k 个元素在交集中,g(k) 表示 恰好k 个元素在交集中。容易得:

f(k)=(nk)22nk

枚举 恰好k 个元素在交集中,组合数来源于选择 k 个元素中的哪些作为被钦定的元素:

f(m)=k=mn(km)g(k)

反演:

g(m)=k=mn(km)(1)kmf(k)

钦定至少型二项式反演与容斥的关系

钦定至少型二项式反演是广义容斥原理结合第一反演公式的普遍性结果。

鉴于该形式的二项式反演在容斥意义下存在不依赖于任何性质的推导方法,它的应用会更加广泛(从表面上看确实是这样)。

本例中拥有 “k 个(同类)容斥集之交的(带权)大小仅与 k 有关” 的特殊性质,从广义容斥原理的角度看,这相当于 α(k)=1a1<<akn|Ui=1kXai||Ui=1kXai|k 固定的情况下是个定值,因此可以省略掉外层的和式,转为用 (nk) 代替,比如此题中 f(k)=(nk)22nk

如果没有上述特殊性质,可以考虑用 DP 计算 f(k)

P5505 [JSOI2011] 分特产

Problem

m 种特产分给 n 个同学,第 i 种特产有 ai 个,要求每个同学必须分到至少一个特产,求方案数对 109+7 取模。输入的数大小不超过 1000

Solution

f(k) 表示 钦定至少 k 个同学没有分到特产,g(k) 表示 恰好 k 人没有分到特产。

Ans=g(0)=i=0n(1)if(i)

其实是扩展容斥原理作为经典容斥原理的特殊形式,这我们之前已经提到过了。

f(k)=(nk)i=1m(ai+nk1nk1)

代入即可。

P4859 已经没有什么好害怕的了

Problem

给出两个长度均为 n 的序列 AB ,保证这 2n 个数互不相同。

现要将 A 序列中的数与 B 序列中的数两两配对,求 “ Ai>Bi 的对数比 Ai<Bi 的对数恰好多 k ” 的配对方案数模 109+9n2000

Solution

如果 n+k 为奇数则无解,否则可以直接解出 Ai>Bi 的对数为 n+k2,这里记为 m

f(k) 表示 钦定至少 kAi>Bi 的方案数,g(k) 表示 恰好 kAi>Bi 的方案数。f(k)=i=kn(ik)g(i)。反演:

g(m)=i=mn(1)im(im)f(i)

A,B 从小到大排序,设 dpi,j 表示考虑 A 的前 i 个数,共钦定了 jAi>Bi 的方案数(不考虑非钦定元素),则 f(k)=(nk)!dpn,k。转移:

dpi,jdpi1,jdpi,j+1(cntj)×dpi1,j

其中 cnt 表示 B 中当前 <Ai​ 的数的数量。代入即可。

P6478 [NOI Online #2 提高组] 游戏

Problem

给定一棵 n=2m 个节点的有根树(以 1 为根),有 m 个白点,m 个黑点。

把黑白点两两配对,对于 k[0,n],求出恰好有 k 对点有祖孙关系的方案数。n5×103

Solution

f(k) 表示钦定 k 对祖孙关系的方案数,g(k) 表示恰好 k 对祖孙关系的方案数。f(k)=i=km(ik)g(i)

g(k)=i=km(1)ik(ik)f(i)

dpu,i 表示考虑 u 子树内,共钦定了 i 对祖孙关系的方案数,则 f(k)=(mk)!dp1,k。转移:

dpu,i+jtmpi×dpv,jdpu,i+j+1tmpi×dpv,j, if cntj>0

其中 cnt 表示 v 子树中与 u 节点异色的节点数量。代入即可。

CF285E Positions in Permutations

Problem

称一个 1n 的排列的完美数为有多少个 i 满足 |Pii|=1 。 求有多少个长度为 n 的完美数恰好为 m 的排列。答案对 109+7 取模。

1n1000,0mn

Solution

这完全就是 [AGC005D] ~K Perm Counting 嘛!

P5339 [TJOI2019] 唱、跳、rap和篮球

Problem

我认为我没有必要简化题面。

大中锋的学院要组织学生参观博物馆,要求学生们在博物馆中排成一队进行参观。他的同学可以分为四类:一部分最喜欢唱、一部分最喜欢跳、一部分最喜欢rap,还有一部分最喜欢篮球。如果队列中 k,k+1,k+2,k+3 位置上的同学依次,最喜欢唱、最喜欢跳、最喜欢rap、最喜欢篮球,那么他们就会聚在一起讨论蔡徐坤。大中锋不希望这种事情发生,因为这会使得队伍显得很乱。大中锋想知道有多少种排队的方法,不会有学生聚在一起讨论蔡徐坤。两个学生队伍被认为是不同的,当且仅当两个队伍中至少有一个位置上的学生的喜好不同。由于合法的队伍可能会有很多种,种类数对 998244353​ 取模。

n1000A,B,C,D500A+B+C+Dn

Solution

f(k) 表示 钦定至少 k 个鸡你太美的方案数,g(k) 表示 恰好 k 个鸡你太美的方案数。则:

f(k)=i=k(ik)g(i)

答案为:

Ans=g(0)=i=0(1)if(i)

其实也是扩展容斥原理作为经典容斥原理的特殊形式,这我们之前已经提到过了。

现在来求 f(k)。利用整体法,将一组鸡你太美的 4 个小黑子看成一个整体插到没有在鸡你太美中的人里面去。

S(a,b,c,d,m) 表示将 a 个喜欢唱、b 个喜欢跳、c 个喜欢 rap、d 个喜欢篮球的人中选 m 个人排成队列的方案数,则有:

f(k)=(n3kk)S(Ak,Bk,Ck,Dk,n4k)

S(a,b,c,d,m) 也是容易的,就是 4 个 EGF 凑一块:

m![xm](i=0axii!)(i=0bxii!)(i=0cxii!)(i=0dxii!)

对每个 f(k) 直接暴力多项式可以做到 O(n2logn)

P4491 [HAOI2018] 染色

Problem

有一个长度为 N 的序列,每个位置都可以被染成 M 种颜色中的某一种。

如果恰好出现了 S 次的颜色有 K 种,则小 C 会产生 WK 的愉悦度。

求所有可能的染色方案获得的愉悦度之和对 1004535809 取模的结果。

1N1071M1051S1500Wi<1004535809

Solution

此例展现 NTT 优化二项式反演。

f(k) 表示出现 S 次的颜色 恰好k 种的方案数,g(k) 表示出现 S 次的颜色 钦定k​ 种的方案数。

g(k)=(Mk)(nSk)(Sk)!(S!)k(Mk)nSk

g(k)=i=kM(ik)fS(k) 反演,展开二项式系数得到差卷积:

f(k)=i=kM(1)ik(ik)g(i)=i=kM(1)iki!k!(ik)!g(i)

ai=i!g(i)bi=(1)ii!,则 f(k)=1k!i=kMaibik

ai=aMif(k)=1k!i=kMaMibik=1k!i=0MkaibMki=f(Mk)

即:翻转 a,转为和卷积,翻转乘积,得到答案。

可以线性求出 g 的每一项系数,因此直接套个 NTT 就能求 f 了,对应项乘上 W 求和即为答案 。时间复杂度 O(MlogM)

P5401 [CTS2019] 珍珠

Problem

n 个在范围 [1,D] 内的整数均匀随机变量。

求至少能选出 m 个瓶子,使得存在一种方案,选择一些变量,并把选出来的每一个变量放到一个瓶子中,满足每个瓶子都恰好装两个值相同的变量的概率。

请输出概率乘上 Dn 后对 998244353 取模的值。0m1091n1091D105

Solution

cnti 表示整数 i 的出现数量。一个合法的生成序列应满足:

i=1Dcnti2mi=1D2×cnti22mi=1D(cnticntimod2)2mi=1Dcntimod2n2m

也就是说,出现次数为奇数的数的个数不超过 n2m

n2m<0 时,特判 Ans=0。当 n2mD 时,特判 Ans=Dn

f(k) 表示出现次数为奇数的数 恰好k 个的方案数,g(k) 表示出现次数为奇数的数 钦定k​​​ 个的方案数。显然 Ans=i=0n2mf(i)

经常用 NTT 优化二项式反演的朋友们知道,只需求出所有的 g(k) 即能得到答案,因此我们唯一的任务就是高效计算 g(k)​。

杀杀杀!

g(k)=(Dk)n![xn](exex2)k(ex)Dk=(Dk)n!12k[xn](exex)k×e(Dk)x=(Dk)n!12kj=0k(kj)[xn](ex)kj×(ex)j×e(Dk)x=(Dk)n!12kj=0k(kj)(1)j[xn]e(D2j)x=(Dk)12kj=0k(kj)(1)j(D2j)n=(Dk)k!2kj=0k(1)j(D2j)nj!1(kj)!

卷卷卷。O(nlogn)

P5400 [CTS2019] 随机立方体

Problem

有一个 n×m×l 的立方体,立方体中每个格子上都有一个数,如果某个格子上的数比三维坐标至少有一维相同的其他格子上的数都要大的话,我们就称它是极大的。现在将 1n×m×ln×m×l 个数等概率随机填入 n×m×l 个格子(即任意数字出现在任意格子上的概率均相等),使得每个数恰出现一次,求恰有 k 个极大的数的概率。答案对 998244353 取模。1n,m,l50000001k1001T10

Solution

神仙题。

f(k) 表示 恰好k 个极大数的概率,g(k) 表示 钦定k 个极大数的概率。答案为 f(k)

g(k)=i=kmin(n,m,l)(ik)f(i)f(k)=i=kmin(n,m,l)(ik)(1)ikg(i)

经常玩 Minecraft 的人知道,我们是可以使用 Minecraft 直观体会如何计算 f(k) 的。cmd 的题解

ai=(ni)(mi)(li)bi=nmlai

钦定 k 个极大值的位置:1k!i=0k1ai

被极大数影响到的位置有 bk 个。

选出被影响到的位置的数值:(nmlbk)

未被影响到的位置可以随便排列:ak!

被影响到的位置可以参考 cmd 的 MC 建模:羊毛代表极大数,方块代表被极大数影响到的位置。不算空气块:|

钦定黄羊毛 < 蓝羊毛 < 红羊毛 < 绿羊毛。一个被影响的位置的颜色是影响到它的最小极大数所代表的颜色,表示我们在这个颜色时将该位置填上数。

然后由内向外填数。对于自外向内的第 i​ 层,需要填上 ai1ai 个数,可以选择的数的数量为 bi

于是将选出的权值填进这 bk 个数内的方案数为:k!i=1k(bi1ai1ai1)(ai1ai1)!k! 是确定极大数的大小顺序,1 是因为选出的 ai1ai 个权值中,最大的权值一定填在当前层极大数的位置上。

最后算概率要除以 (nml)!

整理一下:

g(k)=1(nml)!(1k!i=1kai1)(nmlbk)ak!(k!i=1k(bi1ai1ai1)(ai1ai1)!)=1(nmlbk)!×bk!ak!i=1kai1×(bi1)!(biai1+ai)!=1bk!i=1kai1×(bi1)!bi1!=1bk!i=1kai1×(bi1)!bi1!=1b0!i=1kai1×(bi1)!bi!=i=1kai1bi

使用线性逆元可以做到严格线性:|。但是带老哥也能过。

本文作者:Schucking_Sattin

本文链接:https://www.cnblogs.com/Schucking-Sattin/p/18007448

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   Schucking_Sattin  阅读(115)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起