容斥与二项式反演
感谢 hotpotcondiment 对此页面的贡献
容斥原理
有 \(n\) 个集合 \(S_1,S_2,\cdots,S_n\)(可能集合有交),则至少在一个集合的元素为
应用
考虑一个由多个条件组成的集合 \(S\),且满足这个由多个条件组成的集合 \(p\) 的元素个数为 \(f(p)\),(特别地,记 \(f(\varnothing)\) 为元素的全集 \(U\) 的大小 \(|U|\),感谢 black_trees 的指出)则
满足 \(S\) 中某些条件之一的元素个数为
同时不满足 \(S\) 中任意条件的元素个数为
例题1:CF997C Sky Full Of Stars
题意
有一个 \(n\times n\) 的矩阵,用三种颜色染色,求至少有多少种方案使得至少有一行或一列是同一种颜色,对 \(998244353\) 取模。\(n\le 10^6\)。
解法
考虑容斥。可以把题目中“至少有一行或一列是同一种颜色”的条件看成“满足 \(2n\) 个条件之一:有一行或有一列为同一种颜色”。此时设至少 \(i\) 行和 \(j\) 列为同色的方案数为 \(f(i,j)\),则答案为:
显然有 \(f(i,0)=\binom ni3^i3^{n(n-i)}\),\(f(0,j)=\binom nj3^j3^{n(n-j)}\)。注意 \(ij\ne 0\) 的情况,由于一行的颜色相同决定 \(j\) 列的颜色必须相同,一列的颜色相同决定了 \(i\) 行的颜色必须相同;故而最后 \(i\) 行和 \(j\) 列的颜色必须相同。故而 \(f(i,j)=3\binom ni\binom nj3^{(n-i)(n-j)}\)。
故而答案可以表示成:
前面两项可以 \(O(n)\) 算,关键是优化 \(\sum_{i=1}^n\sum_{j=1}^n(3\binom ni\binom nj3^{(n-i)(n-j)})\) 的计算。考虑把与 \(j\) 无关的项移到 \(\sum_{i=1}^n\) 后,化简则有
其中最后两步用到了二项式定理,即 \((a+b)^n=\sum_{i=0}^n\binom nia^ib^{n-i}\)。
代码
点此查看代码
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn=1000010;
const int md=998244353;
int fac[maxn],inv[maxn];
int n,i,b=1;
inline ll Pow(ll d,ll z){
ll ret=1;
do{
if(z&1) ret=(ret*d)%md;
d=(d*d)%md;
}while(z>>=1);
return ret;
}
inline ll C(int x,int y){return ((1LL*fac[y]*inv[x])%md)*inv[y-x]%md;}
ll ans,tmp,sum;
int main(){
fac[0]=inv[0]=1;
for(i=1;i<maxn;++i) fac[i]=(1LL*fac[i-1]*i)%md;
inv[maxn-1]=Pow(fac[maxn-1],md-2);
for(i=maxn-2;i;--i) inv[i]=(1LL*inv[i+1]*(i+1))%md;
scanf("%d",&n);
for(i=1;i<=n;++i,b=-b){
sum+=b*((C(i,n)*Pow(Pow(3,1LL*n*i),md-2))%md*
(Pow(1-Pow(Pow(3,n-i),md-2),n)-1))%md;
ans+=(2*b*C(i,n)*Pow(3,1LL*n*(n-i)+i))%md;
}
ans=(ans%md+md)%md;
sum=((sum%md+md)%md)*Pow(3,1LL*n*n+1);
printf("%lld",(ans+sum)%md);
return 0;
}
例题2:ARC101E Ribbons on Tree
题意
有一棵大小为 \(n\) 的树。现在要对树上的点两两配对。然后对于每一对点 \((u,v)\),将 \(u\) 到 \(v\) 的简单路径上的所有边染色,定义一组合法的配对方案为能够把树上的所有边染色。求合法的配对方案数,对 \(10^9+7\) 取模。\(n\le 5000\) 且保证 \(n\) 为偶数。
解法
考虑容斥。设整棵树的边集为 \(E\),且令对应的染色的所有边均在 \(E\setminus T\) 边集内的方案数为 \(g(T)\),则可得总合法方案数为 \(\sum_{T\subseteq E}g(T)(-1)^{|T|}\)。(可以把每一条边未被染色视为一个条件,然后需要所有边均被染色)
考虑 \(E\setminus T\) 满足怎样的条件。显然它们构成了 \(|T|+1\) 个连通块,然后每对点均必须在同一个连通块中。
考虑每个连通块内有多少种配对方法。若该连通块大小为 \(S\)(显然 \(S\) 为偶数),则第 \(1\) 个点有 \(S-1\) 个点进行配对,第 \(2\) 个未配对的点有 \(S-3\) 个点进行配对,以此类推可得该连通块内的总配对方案为 \(\prod_{i=1}^{\frac S2}(2i-1)\)(记为 \(h(S)\)。特别地,\(S\) 为奇数时,定义 \(h(S)\) 为 \(0\))。同时若某个选边方案对应的所有连通块的集合为 \(P\),则最后这种选边方案对应的配对方案为 \(\prod_{S\in P}h(S)\)。
考虑使用 dp 计算所有的方案数。设 \(dp_{u,i,j}\) 表示在 \(u\) 的子树内,选定了 \(i\) 个连通块 (不包括 \(\boldsymbol u\) 所在的连通块,因为最后还没有统计好 \(\boldsymbol u\) 所在连通块的大小),且 \(u\) 目前所在的连通块大小为 \(j\) 的对应部分的统计完的连通块的对应方案数。从 \(u\) 的某个儿子 \(v\) 转移时,若选择保留 \((u,v)\) 边,则 \(u\) 和 \(v\) 所在连通块相连接,有 \(dp_{u,i+x,j+y}\leftarrow dp_{u,i+x,j+y}+dp_{u,i,j}dp_{v,x,y}\)。(此时不需要乘上有关 \(h\) 的系数,因为这个连通块还有和其他连通块合并且扩大的机会)否则,由于 \(v\) 所在的连通块只能通过 \((u,v)\) 边继续扩大,故而此时 \(v\) 所在的连通块不会再次扩大,已经统计完成,可以乘上 \(v\) 所在的连通块大小对应的 \(h\) 值,故而有 \(dp_{u,i+x+1,j+y}\leftarrow dp_{u,i+x+1,j+y}+dp_{u,i,j}dp_{v,x,y}h(y)\)。统计完成后,需要将 \(dp_{1,x,y}h(y)\) 累加到 \(dp_{1,x+1,0}\) 上,答案即为 \(\sum_{i=1}^n(-1)^{i-1}dp_{1,i,0}\)。注意:在具体转移 dp 值时,需要把转移的 dp 值赋在另一个数组上,避免之前的 dp 值(现在已经非法,由于我们每次多扫描到 \(u\) 的一棵子树就要强制将其影响体现在 \(dp_u\) 上)造成影响;在转移结束后再赋回来。
这样对于 \(u\) 子树内的 dp,设目前将 dp 值转移到 \(u\) 子树的所有子树大小之和为 \(s\),且即将合并到 \(u\) 子树上的子树大小为 \(t\),则此次统计会对总时间复杂度造成 \(O(s^2t^2)=O(\binom s2\binom t2)\) 的贡献。考虑组合意义,可得总时间复杂度等效于在树上选取任意两对不同的点(但是一对点可以是两个相同的点)的数量(它们会在它们的 lca 处对总时间复杂度有 \(O(1)\) 的贡献),时间复杂度即为 \(O(n^4)\)。空间复杂度显然为 \(O(n^3)\)。
考虑优化。发现其实最后 dp 值对答案的贡献的容斥系数只与连通块的数量的奇偶性有关,所以可以把 \(dp\) 数组的第二维变成选定的连通块的奇偶性(第二维的大小就只会有 \(2\)),同时把第二维涉及的加法转为异或,空间复杂度即可降为 \(O(n^2)\)。
考虑时间复杂度的计算。对于 \(u\) 子树内的 dp,设目前将 dp 值转移到 \(u\) 子树的所有子树大小之和为 \(s\),且即将合并到 \(u\) 子树上的子树大小为 \(t\),则此次统计会对总时间复杂度造成 \(O(st)\) 的贡献。考虑组合意义,可得总时间复杂度等效于在树上选取任意一对不同的点的数量,时间复杂度即为 \(O(n^2)\)。
代码
点此查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxn=5010;
const int md=1000000007;
int n,i,j,u,v,tot;
int head[maxn],ver[maxn<<1],nxt[maxn<<1];
int h[maxn],dp[maxn][2][maxn],tdp[2][maxn];
int dfs(const int p,const int f){
int lp,to,siz=1,ssi;
long long tmp;
dp[p][0][1]=1;
for(lp=head[p];lp;lp=nxt[lp]){
to=ver[lp];
if(to==f) continue;
ssi=dfs(to,p);
for(i=siz;i;--i){
for(j=1;j<=ssi;++j){
tdp[0][i+j]=(1LL*dp[p][0][i]*dp[to][0][j]
+1LL*dp[p][1][i]*dp[to][1][j]+tdp[0][i+j])%md;
tdp[1][i+j]=(1LL*dp[p][0][i]*dp[to][1][j]
+1LL*dp[p][1][i]*dp[to][0][j]+tdp[1][i+j])%md;
tdp[0][i]=((1LL*dp[p][0][i]*dp[to][1][j]
+1LL*dp[p][1][i]*dp[to][0][j])%md*h[j]+tdp[0][i])%md;
tdp[1][i]=((1LL*dp[p][0][i]*dp[to][0][j]
+1LL*dp[p][1][i]*dp[to][1][j])%md*h[j]+tdp[1][i])%md;
}
}
siz+=ssi;
for(i=1;i<=siz;++i){
dp[p][0][i]=tdp[0][i];
dp[p][1][i]=tdp[1][i];
tdp[0][i]=tdp[1][i]=0;
}
}
return siz;
}
int main(){
h[2]=1;
for(i=4;i<maxn;i+=2) h[i]=(1LL*h[i-2]*(i-1))%md;
scanf("%d",&n);
for(i=1;i<n;++i){
scanf("%d%d",&u,&v);
ver[++tot]=v;
nxt[tot]=head[u];
head[u]=tot;
ver[++tot]=u;
nxt[tot]=head[v];
head[v]=tot;
}
dfs(1,0);
u=dp[1][1][0];
v=dp[1][0][0];
for(i=1;i<=n;++i){
u=(u+1LL*h[i]*dp[1][0][i])%md;
v=(v+1LL*h[i]*dp[1][1][i])%md;
}
printf("%d",(u-v+md)%md);
return 0;
}
引入:矩阵相关知识
单位矩阵
定义
满足 \(\forall i,j\in[1,n],I_{i,j}=[i=j]\) 的 \(n\times n\) 的矩阵为 \(n\) 阶单位矩阵。
性质
\(A\times I=I\times A=A\)。
逆矩阵
定义
设 \(A\) 为 \(n\times n\) 的矩阵,若存在一个 \(n\times n\) 的矩阵 \(B\) 满足 \(A\times B=B\times A=I\),则 \(B\) 为 \(A\) 的逆矩阵,记为 \(B=A^{-1}\)(有些矩阵不存在逆矩阵,例如全 \(0\) 矩阵)
高斯-若尔当法求逆矩阵
设 \(A\) 为 \(n\times n\) 的矩阵,可以构造矩阵如下:
将矩阵高斯消元,得到下面的矩阵:
此时 \(B\) 即为 \(A\) 的逆矩阵。
转置矩阵
定义
设 \(A\) 为 \(n\times m\) 的矩阵,若存在一个 \(m\times n\) 的矩阵 \(B\) 满足 \(\forall i\in[1,n],j\in[1,m],A_{i,j}=B_{j,i}\),则 \(B\) 为 \(A\) 的转置矩阵,记为 \(B=A^T\)。
性质
\(A^T\times(A^{-1})^T=I\)。
关系矩阵
定义
已知 \(f\) 和 \(g\) 为定义在 \([1,n]\cap N_+\) 上的函数,构造 \(n\times 1\) 矩阵 \(F\) 和 \(G\) 如下:
若存在一个 \(n\times n\) 的矩阵 \(A\) 满足 \(A\times F=G\),则称 \(A\) 为 \(F\) 到 \(G\) 的关系矩阵。
引入:反演原理
\(F\) 到 \(G\) 的关系矩阵与 \(G\) 到 \(F\) 的关系矩阵互为逆矩阵。
证明:
设 \(A\) 为 \(F\) 到 \(G\) 的关系矩阵,\(B\) 为 \(G\) 到 \(F\) 的关系矩阵,则 \(B\times G=F\),\(A\times F=G\),从而有 \(B\times A\times F=F\),\(B\times A\times F\times F^{-1}=F\times F^{-1}=I\)。由矩阵乘法满足结合律即有 \(B\times A=I\)。
二项式反演
内容
其中 \(m\) 可以为任何的大于 \(n\) 的常数,甚至可以是正无穷。
证明
令 \(F=\begin{bmatrix}f(0)\\f(1)\\\vdots\end{bmatrix}\),\(G=\begin{bmatrix}g(0)\\g(1)\\\vdots\end{bmatrix}\)。
\((1)\):显然 \(F\) 到 \(G\) 和 \(G\) 到 \(F\) 的关系矩阵均为 \(A\)(其中 \(A_{i,j}=(-1)^{j-1}\binom {i-1}{j-1}\))。现在只需要证明 \(A\times A=I\) 即可。推式子可得如下:
上式中第 \(3\sim 5\) 步可以用组合意义直接得出。
\((3)\):\(F\) 到 \(G\) 的关系矩阵 \(A\) 满足 \(A_{i,j}=\binom{i-1}{j-1}\),\(G\) 到 \(F\) 的关系矩阵 \(B\) 满足 \(B_{i,j}=(-1)^{i-j}\binom{i-1}{j-1}\)。推式子可得如下:
\((2)\):因为 \(F\) 到 \(G\) 和 \(G\) 到 \(F\) 的关系矩阵均为 \(A\)(其中 \(A_{i,j}=(-1)^{j-1}\binom{j-1}{i-1}\)),考虑把 \(A\) 转置得 \((A^T)_{i,j}=(-1)^{i-1}\binom{i-1}{j-1}\)。然后推式子基本与 \((1)\) 的证明中的内容相似,只不过第六步为 \(\binom{i-1}{i-j}\sum_{k=0}^{i-j}(\binom{i-j}k(-1)^{i-j-k}1^k)\)。同样可以证明 \(A^T\times A^T=I\),所以 \(A\times A=I\)。
\((4)\):因为 \(F\) 到 \(G\) 的关系矩阵 \(A\) 满足 \(A_{i,j}=\binom{j-1}{i-1}\),\(G\) 到 \(F\) 的关系矩阵 \(B\) 满足 \(B_{i,j}=(-1)^{i-j}\binom{j-1}{i-1}\);所以 \((A^T)_{i,j}=\binom{i-1}{j-1}\),\((B^T)_{i,j}=(-1)^{i-j}\binom{i-1}{j-1}\)。由 \((3)\) 的证明过程可得 \(A^T\times B^T=I\),所以 \(A\times B=I\)。
用法
某些题目可能需要求 恰好 满足若干条件的元素数量,但是 钦定 满足若干条件的元素数量更为好求,则可以通过二项式反演求得 恰好 满足若干条件的元素数量。
例如:设恰好满足 \(i\) 个条件的元素个数为 \(f(i)\),钦定 满足 \(i\) 个条件的元素个数为 \(g(i)\),(条件总共有 \(n\) 个)则一般有:
使用二项式反演简化计算即可。
注意:钦定 满足若干条件的元素中可能有重复的情况,和 至少 不同。
同时由于 \((3),(4)\) 式中左式不带有形如 \((-1)^i\) 的系数,所以较 \((1),(2)\) 式更为常用。
例题1:错排问题
题意
求长为 \(n\) 的 \(\{1,2,\cdots,n\}\) 的排列 \(P\) 的数量,需要满足 \(\forall i\in[1,n],P_i\ne i\)。
解法
设 \(f_i\) 为恰好有 \(i\) 个数满足 值 = 下标 的排列数,\(g_i\) 为钦定有 \(i\) 个数满足 值 = 下标 的排列数;则显然有 \(g_i=\binom ni(n-i)!\) 且 \(g_i=\sum_{j=i}^n\binom jif_j\)。由二项式反演可得 \(f_i=\sum_{j=i}^n(-1)^{j-i}\binom jig_j\)。故而 \(f_0=\sum_{j=0}^n(-1)^jg_j=n!\sum_{j=0}^n\frac{(-1)^j}{j!}\)。这样同时变相地证明了容斥原理的一个公式。
例题2:CF1342E Placing Rooks
题意
在 \(n\times n\) 的国际象棋棋盘上放 \(n\) 个车,要求满足两个条件:
- 所有的空格子都能被至少一个车攻击到。
- 恰好有 \(k\) 对车可以互相攻击到。
答案对 \(998244353\) 取模。\(1\le n\le 2\cdot 10^5,0\le k\le \frac{n(n-1)}2\)。
解法
显然必须要每一行或每一列需要有车放置,否则一定存在有格子不会被任何车攻击到。同时若存在 \(k\) 行不放车(\(k\ne 0\)),由于车的总数为 \(n\),故而必须在每一列放恰好一个车,同时一定有 \(k\) 对车能相互攻击;存在 \(k\) 列不放车同理。下面只讨论存在不放车的行的情况。
设 \(f_i\) 为存在恰好 \(i\) 行不放车的方案数,\(g_i\) 为钦定 \(i\) 行不放车的方案数;则 \(g_i=\sum_{j=i}^nf_j\binom ji\),同时由于可以任选 \(i\) 行且 \(n\) 个车均有 \(n-i\) 种选择(可能存在有不放车的行),则 \(g_i=\binom ni(n-i)^n\)。故而 \(f_i=\sum_{j=i}^n(-1)^{j-i}g_j\binom ji=\sum_{j=i}^n(-1)^{j-i}\binom ji\binom nj(n-j)^n\)。
代码
点此查看代码
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn=200010;
const int md=998244353;
int n,i,b=1,t;
int fac[maxn],inv[maxn];
ll k,ans;
inline int Pow(int d,int z){
int ret=1;
do{
if(z&1) ret=(1LL*ret*d)%md;
d=(1LL*d*d)%md;
}while(z>>=1);
return ret;
}
inline int C(int x,int y){return ((1LL*fac[y]*inv[x])%md*inv[y-x])%md;}
int main(){
fac[0]=1;
for(i=1;i<maxn;++i) fac[i]=(1LL*fac[i-1]*i)%md;
inv[maxn-1]=Pow(fac[maxn-1],md-2);
for(i=maxn-1;i;--i) inv[i-1]=(1LL*inv[i]*i)%md;
scanf("%d%lld",&n,&k);
if(k>=n){
putchar('0');
return 0;
}
for(i=k;i<n;++i,b=-b) ans+=b*(((1LL*C(k,i)*C(i,n))%md)*Pow(n-i,n)%md);
printf("%lld",((ans+ans*(!!k))%md+md)%md);
return 0;
}
例题3:P6478 [NOI Online #2 提高组] 游戏
题意
给定一棵 \(2m\) 个节点的树,其中有 \(m\) 个白点,\(m\) 个黑点。现在要将黑白两点两两配对,对于 \(\forall k\in[0,m]\),求恰好有 \(k\) 对点存在祖先后代关系的方案数,对 \(998244353\) 取模。\(1\le m\le 2500\)。
解法
设 \(f_i\) 为恰好有 \(i\) 对黑白点配对的方案数,\(g_i\) 为钦定 \(i\) 对黑白点配对的方案数(也就是说能够找出 \(i\) 对黑白点配对的方案数)。显然 \(g_i=\sum_{j=i}^m\binom jif_j\)。
考虑 \(g_i\) 的求法,可以使用树形 dp。设 \(dp_{u,p}\) 表示在 \(u\) 的子树中配对了 \(p\) 对有祖先后代关系的黑白点的方案数,则在求 \(dp_u\) 时,可以顺次把 \(u\) 的每个儿子的 dp 值加入 \(dp_u\),转移有 \(dp_{u,p+q}\leftarrow dp_{u,p+q}+dp_{u,p}dp_{v,q}\)。同时可以处理出 \(u\) 子树内(不包括 \(u\) 的)的黑点个数 \(B_u\),白点个数 \(W_u\);最后考虑 \(u\) 和哪些点配对时,若 \(u\) 为白点,则 \(\forall p<B_u,dp_{u,p+1}\leftarrow dp_{u,p+1}+dp_{u,p}(B_u-p)\)。\(u\) 为黑点同理。注意:最后有 \(\boldsymbol{g_i=(m-i)!dp_{1,i}}\) ,未配对的点任意配对的方案需要算上。 时间复杂度显然为 \(O(m^2)\)。
代码
点此查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxn=5010;
const int md=998244353;
int n,i,j,b,u,v,tot;
int C[maxn][maxn],fac[maxn];
int h[maxn],ver[maxn<<1],nxt[maxn<<1];
int dp[maxn][maxn],cnt[maxn][2],tmp[maxn];
long long ans;
bool col[maxn];
int dfs(const int p,const int f){
int lp,to,siz=1,ssi;
dp[p][0]=1;
for(lp=h[p];lp;lp=nxt[lp]){
to=ver[lp];
if(to==f) continue;
ssi=dfs(to,p);
for(i=0;i<=(siz>>1);++i)
for(j=0;j<=(ssi>>1);++j)
tmp[i+j]=(1LL*dp[p][i]*dp[to][j]+tmp[i+j])%md;
siz+=ssi;
cnt[p][0]+=cnt[to][0];
cnt[p][1]+=cnt[to][1];
for(i=0;i<=(siz>>1);++i){
dp[p][i]=tmp[i]%md;
tmp[i]=0;
}
}
++cnt[p][col[p]];i=cnt[p][!col[p]];j=1;
for(;i;--i) dp[p][i]=(1LL*dp[p][i-1]*(j++)+dp[p][i])%md;
return siz;
}
int main(){
C[0][0]=fac[0]=1;
for(i=1;i<maxn;++i){
C[0][i]=1;
fac[i]=(1LL*fac[i-1]*i)%md;
for(j=1;j<=i;++j){
C[j][i]=C[j][i-1]+C[j-1][i-1];
if(C[j][i]>=md) C[j][i]-=md;
}
}
scanf("%d",&n);getchar();
for(i=1;i<=n;++i) col[i]=getchar()-'0';
for(i=1;i<n;++i){
scanf("%d%d",&u,&v);
ver[++tot]=v;nxt[tot]=h[u];h[u]=tot;
ver[++tot]=u;nxt[tot]=h[v];h[v]=tot;
}
dfs(1,0);n>>=1;
for(i=0;i<=n;++i) dp[1][i]=(1LL*dp[1][i]*fac[n-i])%md;
for(i=0;i<=n;++i){
b=1;
for(j=i;j<=n;++j,b=-b) ans+=(1LL*b*dp[1][j]*C[i][j])%md;
printf("%lld\n",(ans%md+md)%md);
ans=0;
}
return 0;
}
例题4
题意
给定一棵 \(n\) 个点的有标号无根树,你可以进行至多 \(k\) 次操作,每次操作是删除一条边再加上一条边,使得这个图还是一棵树。问能得到多少种本质不同的有标号无根树,答案对 \(10^9+7\) 取模。\(n\le 5000\)。
解法
听说 TopCoder13369 TreeDistance 和 CF917D Stranger Trees 这个题很像。
啊这个东西需要 矩阵树定理 吗
设 \(f_i\) 为删去 \(i\) 条边,重新连接 \(i\) 条边 且不能选择删去的边进行连接 的能得到的不同树的数量;\(g_i\) 为 钦定 删去 \(i\) 条边再连接 \(i\) 条边能得到的不同树的数量。(此处的 钦定 为不考虑去重,例如删去任意 \(i\) 条边再连上同样的 \(i-1\) 条边的同种方案会被考虑 \(i\) 次,故而最终答案是 \(\sum_{i=0}^kf_k\))此时 \(g_i=\sum_{j=0}^i\binom ijf_j\)。
考虑删去 \(k\) 条边会形成 \(k+1\) 个连通块,连接 \(k\) 条边会将这 \(k+1\) 个连通块变成 \(1\) 个;故而如果定好了删边和连边的方案,则具体到操作上可以先删除一条边,再把连接这条边的两个端点最后所在的连通块的边连上;保证了每次操作合法,即可以把删边和连边的操作分开进行。
考虑把这 \(k+1\) 个连通块连接成的树有多少种。
引入:Prüfer 序列
定义
对于一棵大小为 \(n\) 的有标号无根树(\(n\ge 2\)),可以进行若干次操作,每次操作可以把标号最小的叶子节点删除,同时在一个序列末端加入 这个点的父亲,直到最后树上只剩两个点。我们称形成的这个长为 \(n-2\) 的序列为这棵树的 Prüfer 序列。
性质
显然最后整个序列值域为 \([1,n]\) 且一棵树对应着唯一一个 Prüfer 序列。
Cayley 公式:\(n\) 个点的有标号无根树总共有 \(n^{n-2}\) 种。(\(n\) 个点的有标号完全图有 \(n^{n-2}\) 棵生成树)
证明:考虑如何从 Prüfer 序列反推出这棵树。
摘自 OI-wiki:据 Prüfer 序列的性质,我们可以得到原树上每个点的度数。然后你也可以得到度数最小的叶结点编号,而这个结点一定与 Prüfer 序列的第一个数连接。然后我们同时删掉这两个结点的度数。每次我们选择一个度数为 \(1\) 的最小的结点编号,与当前枚举到的 Prüfer 序列的点连接,然后同时减掉两个点的度。到最后我们剩下两个度数为 \(1\) 的点,其中一个是结点 \(n\)。把它们连接起来,则可以构造这棵唯一的树。
故而任意一个 Prüfer 序列均有对应的唯一的树,\(n\) 个点的有标号无根树总共有 \(n^{n-2}\) 种。
另外一个结论:有 \(n\) 棵有标号无根树,第 \(i\) 棵树大小为 \(a_i\),现在要把这 \(n\) 棵树用 \(n-1\) 条边连接起来,得到的本质不同的有标号无根树数量为 \((\sum_{i=1}^na_i)^{n-2}\prod_{i=1}^n a_i\)。
证明:模拟 Prüfer 序列的生成过程。我们可以把每个连通块的“权值”看成是其中最小的点的编号;然后在最后生成的树中每次删除“权值”最小的只与另外一个连通块连接的连通块 \(x\),把与其连接的连通块的连向 \(x\) 的点加入一个序列中。可以发现序列的值域为 \(\sum_{i=1}^na_i\),同时由 Prüfer 序列的构造树的方法可以构造一棵唯一的树;并且由于它没有维护每个连通块在成为叶子节点时向外连边的点(我们称其为连通块的代表元素),故而本质不同的有标号无根树数量为 \((\sum_{i=1}^na_i)^{n-2}\prod_{i=1}^n a_i\)。
故而最后 \(g_i\) 即为把树划分的所有连通块对应的 \(n^{i-1}\prod_{j=1}^{i+1} a_j\) 的和。考虑如何维护 \(\sum \prod_{j=1}^{i+1}a_j\)。
可以使用树形 dp。设 \(dp_{u,x,y}\) 表示在 \(u\) 子树内划分了 \(x\) 个连通块(不包括 \(u\) 所在的未统计完的连通块),且 \(u\) 所在的连通块的大小为 \(y\) 的对应 \(\sum\prod_{j=1}^x a_j\)。转移统计方式可以参考容斥原理部分的 ARC101E Ribbons on Tree 部分题解。同时时间复杂度还是 \(O(n^4)\) 的。
考虑把 \(dp\) 数组的一维进行压缩。由于最后计算 \(g_i\) 需要树上的连通块个数,所以只能压缩 \(y\) 所在一维。有一种不太容易想到的 dp 方式:设 \(dp_{u,x,y}\) 为在 \(u\) 子树上,划分了 \(x\) 个连通块(包括 \(u\) 所在的未统计完的连通块),且 \(y\) 表示是否在 \(u\) 所在的连通块中选择了上述的代表元素;则转移为 \(dp_{u,x+i-1,a|b}\leftarrow dp_{u,x+i-1,a|b}+dp_{u,x,a}dp_{v,i,b}(ab\ne 1)\)(合并 \(u\) 和 \(v\) 所在连通块,注意两者所在的连通块不能同时有代表元素。可以用组合意义理解,也就是 \(\prod_{j=1}^ia_i\) 等效于在每个连通块中选一个点的方案数),\(dp_{u,x+i,a}\leftarrow dp_{u,x+i,a}+dp_{u,x,a}dp_{v,i,1}\)(不合并 \(u\) 和 \(v\) 所在的连通块,注意 \(v\) 所在的连通块必须要统计完成)。初值有 \(dp_{u,1,0}=dp_{u,1,1}=1\)。时间复杂度显然为 \(O(n^2)\)。
(11/23:终于把上面的一些错漏之处改正了,参考了 这篇题解)
代码
点此查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxn=5010;
const int md=1000000007;
int n,i,j,u,v,t;
int h[maxn],g[maxn],pw[maxn],nxt[maxn];
int f[maxn][2],dp[maxn][maxn][2],C[maxn][maxn];
inline void Add(int &x,int y){x-=((x+=y)>=md)*md;}
int dfs(int p){
dp[p][1][0]=dp[p][1][1]=1;
int to,sz,rt=1;
for(to=h[p];to;to=nxt[to]){
sz=dfs(to);
for(i=1;i<=rt;++i){
for(j=1;j<=sz;++j){
Add(f[i+j][0],(1LL*dp[p][i][0]*dp[to][j][1])%md);
Add(f[i+j][1],(1LL*dp[p][i][1]*dp[to][j][1])%md);
Add(f[i+j-1][0],(1LL*dp[p][i][0]*dp[to][j][0])%md);
Add(f[i+j-1][1],(1LL*dp[p][i][0]*dp[to][j][1]+
1LL*dp[p][i][1]*dp[to][j][0])%md);
}
}
rt+=sz;
memcpy(dp[p],f,sizeof(f));
memset(f,0,sizeof(f));
}
return rt;
}
class TreeDistance{
public:
int countTrees(vector<int> p,int k){
n=p.size()+1;
k=n-1-min(k,n-1);
for(i=2;i<=n;++i){
u=p[i-2]+1;
nxt[i]=h[u]; h[u]=i;
}
pw[0]=1;
for(i=0;i<=n;++i) C[0][i]=1;
for(i=1;i<=n;++i){
C[0][i]=1; pw[i]=(1LL*pw[i-1]*n)%md;
for(j=i;j<=n;++j)
Add(C[i][j],C[i][j-1]+C[i-1][j-1]);
}
dfs(1);
for(i=0;i<n-1;++i) g[i]=(1LL*dp[1][n-i][1]*pw[n-i-2])%md;
g[n-1]=1; u=0;
for(i=n-1;i>=k;--i){
for(j=i,v=0;j<n;++j){
t=(1LL*C[i][j]*g[j])%md;
if(v) t=md-t; Add(u,t); v=!v;
}
}
return u;
}
};
练习题:
DarkBzoj 2839 集合计数
考虑把交集之外的元素进行考虑,可以看作交集之外的 \(n-k\) 个元素的分配方法分配给 \(p\) 个集合(\(p\ne 0\)),方法显然为 \(\sum_{p=1}^{2^{n-k}}\binom{2^{n-k}}p=2^{2^{n-k}}-1\)(分配方法包括不分配任何元素)。二项式反演即可。
注意:\(\boldsymbol{2^{n-k}}\) 部分需要对 \(\boldsymbol{10^9+7-1}\) 取模(\(\boldsymbol{2^{10^9+7-1}\equiv1}\ \bold{mod}\ \boldsymbol{({10^9+7})}\))。(这个部分卡了我很久)
P4859 已经没有什么好害怕的了
升序排序,模拟匹配过程。发现可以用双指针 + dp 得出钦定有 \(c\) 个糖果的能量大于药片的方案数,二项式反演即可。
P5505 分特产
考虑容斥。在至少有 \(c\) 个同学分不到特产的情况下,分开讨论每种特产的分配方案(即把无标号球放进有标号盒子且能有空盒的方案数)。
CF1228E Another Filling the Grid
讨论如何放 \(1\) 的情况比较困难(至少我只知道 \(O(n^4)\) 的方法),可以讨论如何放 \(2\sim k\) 的元素。然后容斥即可,做法可参考 CF997C。
本文来自博客园,作者:Fran-Cen,转载请注明原文链接:https://www.cnblogs.com/Fran-CENSORED-Cwoi/p/16753845.html