二项式反演 系列 题解
有时候做一些计数题,会遇到“恰好满足……”类似的条件,往往无法正面击破。但是有可能我们掌握的现有的排列组合 trick 们,可以解决“至少……满足……”的条件,即钦定某些满足……,其它不管。而二项式反演这个既定事实可以实现二者方案数的转化。
二项式反演
我们设 f i f_i fi 表示钦定 i i i 个元素满足某些条件的方案数(其它元素不一定满足), g i g_i gi 表示恰好 i i i 个元素满足某些条件的方案数。那么有既定的关系。
形式一
f n = ∑ i = n m ( i n ) g i ⇔ g n = ∑ i = n m ( − 1 ) i − n ( i n ) f i f_n=\sum_{i=n}^m\binom{i}{n}g_i\Leftrightarrow g_n=\sum_{i=n}^m(-1)^{i-n}\binom{i}{n}f_i fn=i=n∑m(ni)gi⇔gn=i=n∑m(−1)i−n(ni)fi
形式二
f n = ∑ i = 0 n ( n i ) g i ⇔ g n = ∑ i = 0 n ( − 1 ) n − i ( n i ) f i f_n=\sum_{i=0}^n\binom{n}{i}g_i\Leftrightarrow g_n=\sum_{i=0}^n(-1)^{n-i}\binom{n}{i}f_i fn=i=0∑n(in)gi⇔gn=i=0∑n(−1)n−i(in)fi
证明略,可以参考 OI-WIKI。
实际做题的时候我们发现二者的使用场景和效果略有不同。不过其它的博客似乎并没有很详细的说明,其实可以略微讨论一下。
应用场景区分
形式一适用场景
- 问题类型:从“至少满足 k k k 个条件”到“恰好满足 k k k 个条件”的转换。
- 典型问题:
- 错位排列计数;
- 动态规划中“包含冗余状态”的精确计数优化。
- 已知至少选k个元素的总情况 f ( k ) f(k) f(k),反推恰好选i个元素的情况 g ( i ) g(i) g(i)。
形式二适用场景
- 问题类型:从“全集并集”到“单个集合”的反推。
- 典型问题:
- 容斥原理中的交并集转换(如求多个集合的并集大小);
- 严格满足所有约束的情况计数。
- 已知多个集合的并集大小 f ( n ) f(n) f(n),反推单个集合的交集大小 g ( n ) g(n) g(n)。
组合意义对比
方向 | 形式一 | 形式二 |
---|---|---|
核心意义 | 分层消除冗余(从高阶到低阶) | 整体符号反转去重(整体到部分) |
组合数作用 | 向下投影: ( i k ) \displaystyle\binom{i}{k} (ki) | 同层展开: ( n i ) \displaystyle\binom{n}{i} (in) |
符号项意义 | 消除高阶项的叠加贡献 ( − 1 ) i − k (-1)^{i-k} (−1)i−k | 消除重复计数( ( − 1 ) n − i (-1)^{n-i} (−1)n−i) |
因此在二项式反演的最广泛的应用场景,“钦定”和“恰好”的转换中,最常用形式一。 f f f 为钦定, g g g 为恰好,那么:
f n = ∑ i = n m ( n i ) g i ⇔ g n = ∑ i = n m ( − 1 ) i − n ( n i ) f i f_n=\sum_{i=n}^m\binom{n}{i}g_i\Leftrightarrow g_n=\sum_{i=n}^m(-1)^{i-n}\binom{n}{i}f_i fn=i=n∑m(in)gi⇔gn=i=n∑m(−1)i−n(in)fi
1.洛谷 P5505 分特产
题意
JYY 带回了 m m m 种特产,每种特产有 a i a_i ai 个,要分给实验室的同学们。他想知道,把这些特产分给 n n n 个同学,一共有多少种不同的分法?当然,JYY 不希望任何一个同学因为没有拿到特产而感到失落,所以每个同学都必须至少分得一个特产。
分法方案数对 1 0 9 + 7 10^9+7 109+7 取模。
1 ≤ n , m ≤ 1000 1\le n,m\le 1000 1≤n,m≤1000。
思路
先正难则反,考虑转化问题为,恰好 0 0 0 个同学没有分到产品,即 g 0 g_0 g0。那么我们钦定 f i f_i fi 表示钦定 i i i 个同学没有分到特产。
我们钦定 i i i 个人没有得到特产 ( n i ) \displaystyle\binom{n}{i} (in),对于每种特产 a j a_j aj,我们可以随意分给剩下的 n − i n-i n−i 个人,不管有或没有特产。
这里我们使用“插板法”:我们把 a j a_j aj 分成 n − i n-i n−i 部分,但是可以留空。我们插“多余板” n − i n-i n−i 个,转化为把 a j + ( n − i ) a_j+(n-i) aj+(n−i) 分成 n − i n-i n−i 部分,不可以留空,这个问题就好了: ( a j + ( n − i ) − 1 ( n − i ) − 1 ) \displaystyle\binom{a_j+(n-i)-1}{(n-i)-1} ((n−i)−1aj+(n−i)−1)。
那么:
f i = ( n i ) ( a j + ( n − i ) − 1 ( n − i ) − 1 ) f_i=\binom{n}{i}\binom{a_j+(n-i)-1}{(n-i)-1} fi=(in)((n−i)−1aj+(n−i)−1)
我们用形式一转成 g 0 g_0 g0 即可。
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=3003,mod=1e9+7;
ll n,m,a[N];
ll C[N][N];
ll f[N];
void init()
{for(int i=1;i<N;i++)C[i][0]=C[i][i]=1;for(int i=1;i<N;i++)for(int j=1;j<i;j++)C[i][j]=(C[i-1][j-1]+C[i-1][j])%mod;
}
int main()
{init();scanf("%lld%lld",&n,&m);for(int i=1;i<=m;i++)scanf("%lld",&a[i]);for(int i=0;i<=n;i++){f[i]=C[n][i];for(int j=1;j<=m;j++)f[i]=f[i]*C[a[j]+n-i-1][n-i-1]%mod;}ll ans=0;for(int i=0;i<=n;i++){ll op=(i&1?-1:1);ans=(ans+op*f[i]+mod)%mod;}printf("%lld",ans);return 0;
}
2.洛谷 P10596/BZOJ2839 集合计数
思路
一个有 n n n 个元素的集合有 2 n 2^n 2n 个不同子集(包含空集),现在要在这 2 n 2^n 2n 个集合中取出若干集合(至少一个),使得它们的交集的元素个数为 m m m,求取法的方案数,答案模 1 0 9 + 7 10^9+7 109+7。
1 ≤ n ≤ 1000000 1\leq n\leq 1000000 1≤n≤1000000, 0 ≤ m ≤ n 0\leq m\leq n 0≤m≤n。
思路
我们考虑枚举交集大小。钦定 i i i 个元素作为交集,剩下的 n − i n-i n−i 个随便排,不管有没有在交集里面。那么剩下的 n − i n-i n−i 个能组成 2 n − i 2^{n-i} 2n−i 个集合,这 2 n − i 2^{n-i} 2n−i 个集合各自可以选可以不选,但是不能全部不选(不然就算上空集了),所以有 2 2 n − i − 1 2^{2^{n-i}}-1 22n−i−1 种选法。
设 f i f_i fi 表示钦定 i i i 个元素作为交集(其它概念同上)的方案数, g i g_i gi 表示恰有 i i i 个元素作为交集的方案数,那么有:
f i = ( n i ) ( 2 2 n − i − 1 ) f_i=\binom{n}{i}\left(2^{2^{n-i}}-1\right) fi=(in)(22n−i−1)
然后用形式一转成 g m g_m gm 即可。
有几点要注意的,首先那个二的二的次幂比较恐怖,快速幂可能干到 2 1 0 9 + 6 2^{10^9+6} 2109+6,还是预处理稳妥;然后因为 k ≤ 0 k\le 0 k≤0,所以钦定的时候记得计算交集为空集的方案。具体细节见代码。
代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=1e6+9,mod=1e9+7;
ll n,m;
ll f[N];
ll qpow(ll x,ll k)
{ll ret=1;while(k){if(k&1)ret=ret*x%mod;x=x*x%mod;k>>=1;}return ret;
}
ll fac[N],inv[N],pp2[N];
void init()
{pp2[0]=2;for(int i=1;i<=n;i++)pp2[i]=pp2[i-1]*pp2[i-1]%mod;fac[0]=1;for(int i=1;i<N;i++)fac[i]=fac[i-1]*i%mod;inv[N-1]=qpow(fac[N-1],mod-2);for(int i=N-2;i>=0;i--)inv[i]=inv[i+1]*(i+1)%mod;
}
ll C(ll n,ll m)
{if(n<m)return 0;return fac[n]*inv[m]%mod*inv[n-m]%mod;
}
int main()
{scanf("%lld%lld",&n,&m);init();for(int i=0;i<=n;i++)f[i]=C(n,i)*(pp2[n-i]-1)%mod;ll gm=0;for(int i=m;i<=n;i++){ll op=((i-m)&1?-1:1);gm=(gm+op*C(i,m)%mod*f[i]%mod+mod)%mod;}printf("%lld",gm);return 0;
}
3.洛谷 P4859 已经没有什么好害怕的了
题意
Charlotte 的结界中有两种具有能量的元素,一种是“糖果”,另一种是“药片”,各有 n n n 个。在 Charlotte 发动进攻前,“糖果”和“药片”会两两配对,若恰好糖果比药片能量大的组数比“药片”比“糖果”能量大的组数多 k k k 组,则在这种局面下,Charlotte 的攻击会丟失,从而 Mami 仍有消灭 Charlotte 的可能。
你必须根据 Homura 告诉你的“糖果”和“药片”的能量的信息迅速告诉 Homura 这种情况的个数。
保证“糖果”和“药片”的能量参数各不相同。答案对 1 0 9 + 9 10^9+9 109+9 取模。
1 ≤ n ≤ 2000 1 \le n \le 2000 1≤n≤2000, 0 ≤ k ≤ n 0 \le k \le n 0≤k≤n。
思路
看到 n n n 支持 Θ ( n 2 ) \Theta(n^2) Θ(n2),而且属于两个数组进行匹配的问题,dp 时可以设置二维状态。
我们看到题目,根据题目要求动用小奥知识,“恰好糖果比药片能量大的组数”应为 m = n + k 2 m=\dfrac{n+k}{2} m=2n+k。
那么我们考虑枚举每个 a i a_i ai,每次计算可以添加多少 b j b_j bj,从而计算出,当前配对有前 i i i 个 a a a 数组元素,有 j j j 个 b b b 数组元素小于 i i i 个 a a a 数组元素的方案数,设为 F i , j F_{i,j} Fi,j。
考虑向 F i , j F_{i,j} Fi,j 转移,如果添加一个 a a a 就 F i , j ← F i − 1 , j F_{i,j}\leftarrow F_{i-1,j} Fi,j←Fi−1,j;如果从 j − 1 j-1 j−1 个转移到 j j j 个,那么我们要找比 a i a_i ai 小的在 b b b 数组中还有多少个没有被选。
这样算配对数,其实和 a i a_i ai 的加入顺序无关,是个组合。因此我们对 a , b a,b a,b 数组从小到大排序,那么当前 F i , j F_{i,j} Fi,j 代表的状态合法的话,总是满足 a i > b 1 ∼ j a_i>b_{1\sim j} ai>b1∼j。
刚刚说我们要找“比 a i a_i ai 小的在 b b b 数组中还有多少个没有被选”,因为此时 a , b a,b a,b 数组均有序,所以我们可以用双指针计算比 a i a_i ai 小的数在 b b b 数组中总共有 l e s i les_i lesi 个。因为在合法状态 F i , j − 1 F_{i,j-1} Fi,j−1 中,比 a i a_i ai 小的有 j − 1 j-1 j−1 个,那么还有 l e s i − ( j − 1 ) les_i-(j-1) lesi−(j−1) 个比 a i a_i ai 小的没选。那么:
F i , j ← F i , j − 1 × [ l e s i − ( j − 1 ) ] F_{i,j}\leftarrow F_{i,j-1}\times[les_i-(j-1)] Fi,j←Fi,j−1×[lesi−(j−1)]
初始化 F 0 , 0 = 1 F_{0,0}=1 F0,0=1:
sort(a+1,a+n+1);
sort(b+1,b+n+1);
ll pos=1;
for(int i=1;i<=n;i++)
{while(pos<=n&&b[pos]<a[i])pos++;les[i]=pos-1;
}
F[0][0]=1;
for(int i=1;i<=n;i++)
for(int j=0;j<=i;j++)
F[i][j]=(F[i-1][j]+F[i-1][j-1]*(les[i]-(j-1)+mod)%mod)%mod;
我们设 f i f_i fi 表示钦定了 i i i 个要小于配对中的 a a a 数组元素,剩下的 n − i n-i n−i 个随便大于小于; g i g_i gi 表示恰好有 i i i 小于配对中的 a a a 数组元素。那么就是钦定的 F n , i F_{n,i} Fn,i,后面乱排 ( n − i ) ! (n-i)! (n−i)!,所以:
f i = F n , i × ( n − i ) ! f_i=F_{n,i}\times (n-i)! fi=Fn,i×(n−i)!
用形式一转回 g m g_m gm 即可, m = n + k 2 m=\dfrac{n+k}{2} m=2n+k。
注意模数。
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=2002,mod=1e9+9;
ll n,k,m,a[N],b[N];
ll les[N],F[N][N],f[N],gm;
//F(i,j):当前匹配,a的前i个数当中,有j个b中数小于a的方案数
ll C[N][N],fac[N];
void init()
{for(int i=0;i<N;i++)C[i][0]=C[i][i]=1;for(int i=1;i<N;i++)for(int j=1;j<i;j++)C[i][j]=(C[i-1][j]+C[i-1][j-1])%mod;fac[0]=1;for(int i=1;i<N;i++)fac[i]=fac[i-1]*i%mod;
}
int main()
{init();scanf("%lld%lld",&n,&k);for(int i=1;i<=n;i++)scanf("%lld",&a[i]);for(int i=1;i<=n;i++)scanf("%lld",&b[i]);sort(a+1,a+n+1);sort(b+1,b+n+1);if((n+k)&1){puts("0");return 0; }m=(n+k)/2;ll pos=1;for(int i=1;i<=n;i++){while(pos<=n&&b[pos]<a[i])pos++;les[i]=pos-1;}F[0][0]=1;for(int i=1;i<=n;i++)for(int j=0;j<=i;j++)F[i][j]=(F[i-1][j]+F[i-1][j-1]*(les[i]-(j-1)+mod)%mod)%mod;for(int i=1;i<=n;i++)f[i]=F[n][i]*fac[n-i]%mod;//剩下钦定了i个要小于a中的数,剩下n-i个随便排for(int i=m;i<=n;i++){ll op=((i-m)&1?-1:1);gm=(gm+op*f[i]%mod*C[i][m]%mod+mod)%mod;//用形式2还原 }printf("%lld",gm);return 0;
}
4.洛谷 P6478 游戏
题意
小 A 和小 B 正在玩一个游戏:有一棵包含 n = 2 m n=2m n=2m 个点的有根树,它的根是 1 1 1 号点,初始时两人各拥有 m m m 个点。游戏的每个回合两人都需要选出一个自己拥有且之前未被选过的点,若对手的点在自己的点的子树内,则该回合自己获胜;若自己的点在对方的点的子树内,该回合自己失败;其他情况视为平局。游戏共进行 m m m 回合。
作为旁观者的你只想知道,在他们随机选点的情况下,第一次非平局回合出现时的回合数的期望值。
为了计算这个期望,你决定对于 k = 0 , 1 , 2 , ⋯ , m k=0,1,2,\cdots,m k=0,1,2,⋯,m,计算出非平局回合数为 k k k 的情况数,即在 k k k 局后平局了。两种情况不同当且仅当存在一个小 A 拥有的点 x x x,小 B 在 x x x 被小 A 选择的那个回合所选择的点不同。
由于情况总数可能很大,你只需要输出答案对 998244353 998244353 998244353 取模后的结果。
n ≤ 5000 n\le 5000 n≤5000。
思路
我们先钦定 i i i 局为非平局,后面的是不是平局均可。设 f i f_i fi 表示至少 i i i 局非平局的方案数, g i g_i gi 表示恰好 i i i 局是非平局然后就平局了的方案数。
这题还是个树形 dp,我们需要知道树上会出现多少非平局。我们不妨设 F u , x F_{u,x} Fu,x 表示 u u u 子树内出现了多少非平局,非平局出现的条件是存在一对父子颜色不同。
对于不包括 v v v 子树的子树 u u u 和子树 v v v,其中的分别有 x x x、 y y y 对非平局点对,那么对整个 u u u 子树贡献到 x + y x+y x+y 个点对的话,根据乘法原理,其方案数有 F u , x + y = F u , x × F v , y F_{u,x+y}=F_{u,x}\times F_{v,y} Fu,x+y=Fu,x×Fv,y,不过我们发现如果直接更新 F u , x + y F_{u,x+y} Fu,x+y 会和式子中的 F u , x F_{u,x} Fu,x 有冲突,因此使用辅助数组记录:
for(int x=0;x<=siz[u]+siz[v];x++)
tem[x]=0;
for(int x=0;x<=siz[u];x++)
for(int y=0;y<=siz[v];y++)
tem[x+y]=(tem[x+y]+F[u][x]*F[v][y]%mod)%mod;
for(int x=0;x<=siz[u]+siz[v];x++)
F[u][x]=tem[x];
除了合并其它子树的方案数,我们还可以新加入点对。对于完全体的 u u u 子树,当前已经产生了 x x x 对非平局点对(即状态 F u , x F_{u,x} Fu,x),我们考虑新加 u u u 和其它其它异色点的非平局关系,从 F u , x − 1 F_{u,x-1} Fu,x−1 转移过来,只需要知道 u u u 子树内还有多少和 u u u 异色的点,且这些点没有和其它异色点产生非平局关系。
我们记 c n t 1 u cnt1_u cnt1u 表示 u u u 子树内有多少颜色为 1 1 1 的节点,那么颜色为 0 0 0 的 c n t 0 u = s i z u − c n t 1 u cnt0_u=siz_u-cnt1_u cnt0u=sizu−cnt1u。我们不难发现,子树内 x − 1 x-1 x−1 个非平局点对中,肯定有 x − 1 x-1 x−1 个和 u u u 同色的、也有 x − 1 x-1 x−1 个和 u u u 异色的,因此:
- 若 c o l u = 1 col_u=1 colu=1,那么子树内还有 c n t 0 − ( x − 1 ) cnt0-(x-1) cnt0−(x−1) 个异色点可以选;
- 若 c o l u = 0 col_u=0 colu=0,那么子树内还有 c n t 1 − ( x − 1 ) cnt1-(x-1) cnt1−(x−1) 个异色点可以选。
因此:
{ F u , x ← F u , x − 1 × [ c n t 0 − ( x − 1 ) ] , c o l u = 1 F u , x ← F u , x − 1 × [ c n t 1 − ( x − 1 ) ] , c o l u = 0 \left\{\begin{matrix} F_{u,x}\leftarrow F_{u,x-1}\times[cnt0-(x-1)],col_u=1\\ F_{u,x}\leftarrow F_{u,x-1}\times[cnt1-(x-1)],col_u=0 \end{matrix}\right. {Fu,x←Fu,x−1×[cnt0−(x−1)],colu=1Fu,x←Fu,x−1×[cnt1−(x−1)],colu=0
树形 dp 就告一段落。我们回到“钦定恰好”的状态: F 1 , i F_{1,i} F1,i 表示,在整棵树内有 i i i 对非平局点对,因为是钦定,剩下 m − i m-i m−i 对( m − i m-i m−i 个 1 1 1 色和 m − i m-i m−i 个 0 0 0 色)随便排,那么:
f i = F 1 , i × ( m − i ) ! f_i=F_{1,i}\times (m-i)! fi=F1,i×(m−i)!
用形式一还原出 g 0 ∼ m g_{0\sim m} g0∼m 即可, a n s = ∑ i = 0 m g i ans=\displaystyle\sum_{i=0}^mg_i ans=i=0∑mgi。
代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=5002,mod=998244353;
char s[N];
ll n,m;
bool col[N];
ll idx,head[N];
struct edge
{ll to,next;
}e[N<<1];
void addedge(ll u,ll v)
{idx++;e[idx].to=v;e[idx].next=head[u];head[u]=idx;
}
ll C[N][N],fac[N];
void init()
{for(int i=0;i<N;i++)C[i][0]=C[i][i]=1;for(int i=1;i<N;i++)for(int j=1;j<i;j++)C[i][j]=(C[i-1][j]+C[i-1][j-1])%mod;fac[0]=1;for(int i=1;i<N;i++)fac[i]=fac[i-1]*i%mod;
}
ll siz[N],cnt1[N],F[N][N],tem[N];
ll f[N],g[N];
void dfs(ll u,ll fa)
{siz[u]=1;cnt1[u]=col[u];F[u][0]=1;for(int i=head[u];i;i=e[i].next){ll v=e[i].to;if(v==fa)continue;dfs(v,u);for(int x=0;x<=siz[u]+siz[v];x++)tem[x]=0;for(int x=0;x<=siz[u];x++)for(int y=0;y<=siz[v];y++)tem[x+y]=(tem[x+y]+F[u][x]*F[v][y]%mod)%mod;for(int x=0;x<=siz[u]+siz[v];x++)F[u][x]=tem[x];siz[u]+=siz[v];cnt1[u]+=cnt1[v];}ll n1=cnt1[u],n0=siz[u]-cnt1[u];for(int x=min(n1,n0);x>=1;x--){if(col[u])F[u][x]=(F[u][x]+F[u][x-1]*(n0-(x-1))%mod)%mod;else F[u][x]=(F[u][x]+F[u][x-1]*(n1-(x-1))%mod)%mod;}
}
int main()
{init();scanf("%lld%s",&n,s+1);for(int i=1;i<=n;i++)col[i]=(ll)s[i]-'0';m=n/2;for(int i=1;i<n;i++){ll u,v;scanf("%lld%lld",&u,&v);addedge(u,v);addedge(v,u);}dfs(1,0);for(int i=0;i<=m;i++)f[i]=F[1][i]*fac[m-i]%mod;ll ans=0;for(int i=0;i<=m;i++){for(int j=i;j<=m;j++){ll op=((j-i)&1?-1:1);g[i]=(g[i]+op*f[j]%mod*C[j][i]+mod)%mod;}printf("%lld\n",g[i]);}return 0;
}
5.洛谷 P6076 JSOI2015 染色问题
题意
萌萌家有一个棋盘,这个棋盘是一个 n × m n \times m n×m 的矩形,分成 n n n 行 m m m 列共 n × m n \times m n×m 个小方格。
现在萌萌和南南有 c c c 种不同颜色的颜料,他们希望把棋盘用这些颜料染色,并满足以下规定:
- 棋盘的每一个小方格既可以染色(染成 c c c 种颜色中的一种),也可以不染色。
- 棋盘的每一行至少有一个小方格被染色。
- 棋盘的每一列至少有一个小方格被染色。
- 每种颜色都在棋盘上出现至少一次。
以下是一些将 3 × 3 3 \times 3 3×3 棋盘染成 c = 3 c=3 c=3 种颜色(红、黄、蓝)的例子(下图已更新):
请你求出满足要求的不同的染色方案总数。只要存在一个位置的颜色不同,即认为两个染色方案是不同的。
答案对 1 0 9 + 7 10^9+7 109+7 取模的值。
1 ≤ n , m , c ≤ 400 1 \le n,m,c \le 400 1≤n,m,c≤400。
思路
这里讲二项式反演的思路,纯容斥原理的做法以后再写。
我们看到非常多的“至少一个”,其实和 1. 1. 1. 思考方式差不多,我们仍然正难则反,看行列没有染和颜色没有用。
设 f i , j , t f_{i,j,t} fi,j,t 表示至少 i i i 行 j j j 列没染、至少 t t t 色没用的方案数, g i , j , t g_{i,j,t} gi,j,t 表示恰好有 i i i 行 j j j 列没染、恰好 t t t 色没用的方案数,最终答案就是 g 0 , 0 , 0 g_{0,0,0} g0,0,0。
对于 f i , j , t f_{i,j,t} fi,j,t 先选定那些行列没染和那些颜色没用,然后对于剩下的 ( n − i ) × ( m − j ) (n-i)\times(m-j) (n−i)×(m−j) 个格子,可以填没有被选定的 c − t c-t c−t 色和不填,那么:
f i , j , t = ( n i ) ( m j ) ( c t ) ( c − t + 1 ) ( n − i ) ( m − j ) f_{i,j,t}=\binom{n}{i}\binom{m}{j}\binom{c}{t}(c-t+1)^{(n-i)(m-j)} fi,j,t=(in)(jm)(tc)(c−t+1)(n−i)(m−j)
我们发现如果要转回 g g g,这是一个高维二项式反演,但是其实容易证明:
f x , y , z = ∑ i = x n ∑ j = y m ∑ k = z c ( i x ) ( j y ) ( k z ) g i , j , k ⇔ g x , y , z = ∑ i = x n ∑ j = y m ∑ k = z c ( i x ) ( j y ) ( k z ) ( − 1 ) i + j + k − x − y − z f i , j , k \begin{matrix} f_{x,y,z}=\displaystyle\sum_{i=x}^n\sum_{j=y}^m\sum_{k=z}^c\binom{i}{x}\binom{j}{y}\binom{k}{z}g_{i,j,k}\\ \Leftrightarrow g_{x,y,z}=\displaystyle\sum_{i=x}^n\sum_{j=y}^m\sum_{k=z}^c\binom{i}{x}\binom{j}{y}\binom{k}{z}(-1)^{i+j+k-x-y-z}f_{i,j,k} \end{matrix} fx,y,z=i=x∑nj=y∑mk=z∑c(xi)(yj)(zk)gi,j,k⇔gx,y,z=i=x∑nj=y∑mk=z∑c(xi)(yj)(zk)(−1)i+j+k−x−y−zfi,j,k
那么 g 0 , 0 , 0 = ∑ i = 0 n ∑ j = 0 m ∑ k = 0 c ( − 1 ) i + j + k f i , j , k = ∑ i = 0 n ∑ j = 0 m ∑ k = 0 c ( − 1 ) i + j + k ( n i ) ( m j ) ( c t ) ( c − t + 1 ) ( n − i ) ( m − j ) g_{0,0,0}=\displaystyle\sum_{i=0}^n\sum_{j=0}^m\sum_{k=0}^c (-1)^{i+j+k}f_{i,j,k}=\displaystyle\sum_{i=0}^n\sum_{j=0}^m\sum_{k=0}^c (-1)^{i+j+k}\binom{n}{i}\binom{m}{j}\binom{c}{t}(c-t+1)^{(n-i)(m-j)} g0,0,0=i=0∑nj=0∑mk=0∑c(−1)i+j+kfi,j,k=i=0∑nj=0∑mk=0∑c(−1)i+j+k(in)(jm)(tc)(c−t+1)(n−i)(m−j)。
代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=402,mod=1e9+7;
ll n,m,c;
ll g000;
ll C[N][N];
void init()
{for(int i=0;i<N;i++)C[i][0]=C[i][i]=1;for(int i=1;i<N;i++)for(int j=1;j<i;j++)C[i][j]=(C[i-1][j]+C[i-1][j-1])%mod;
}
ll qpow(ll x,ll k)
{ll ret=1;while(k){if(k&1)ret=ret*x%mod;x=x*x%mod;k>>=1;}return ret;
}
int main()
{init();scanf("%lld%lld%lld",&n,&m,&c);for(int i=0;i<=n;i++){for(int j=0;j<=m;j++){for(int t=0;t<=c;t++){ll op=((i+j+t)&1?-1:1);g000=(g000+op*C[n][i]*C[m][j]%mod*C[c][t]%mod*qpow(c-t+1,(n-i)*(m-j))%mod+mod)%mod;}}}printf("%lld",g000);return 0;
}