容斥与二项式反演

感谢 hotpotcondiment 对此页面的贡献

容斥原理

\(n\) 个集合 \(S_1,S_2,\cdots,S_n\)(可能集合有交),则至少在一个集合的元素为

\[\begin{aligned}&{\color{white}{-\;}}\sum\texttt{在任意一个集合内的元素总和}\\&-\sum\texttt{在任意两个集合交内的元素总和}\\&+\sum\texttt{在任意三个集合交内的元素总和}\\&-\cdots\cdots\end{aligned} \]

应用

考虑一个由多个条件组成的集合 \(S\),且满足这个由多个条件组成的集合 \(p\) 的元素个数为 \(f(p)\),(特别地,记 \(f(\varnothing)\) 为元素的全集 \(U\) 的大小 \(|U|\),感谢 black_trees 的指出)则

满足 \(S\) 中某些条件之一的元素个数为

\[\sum_{p\subseteq S,p\ne\varnothing}(-1)^{|p|+1}f(p) \]

同时不满足 \(S\) 中任意条件的元素个数为

\[\sum_{p\subseteq S}(-1)^{|p|}f(p) \]

例题1:CF997C Sky Full Of Stars

题意

有一个 \(n\times n\) 的矩阵,用三种颜色染色,求至少有多少种方案使得至少有一行或一列是同一种颜色,对 \(998244353\) 取模。\(n\le 10^6\)

解法

考虑容斥。可以把题目中“至少有一行或一列是同一种颜色”的条件看成“满足 \(2n\) 个条件之一:有一行或有一列为同一种颜色”。此时设至少 \(i\) 行和 \(j\) 列为同色的方案数为 \(f(i,j)\),则答案为:

\[\sum_{i=0}^n\sum_{j=0}^n(-1)^{i+j+1}f(i,j)+f(0,0) \]

显然有 \(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)}\)

故而答案可以表示成:

\[\sum_{j=1}^n(-1)^{j+1}\binom nj3^j3^{n(n-j)}+\sum_{i=1}^n(-1)^{i+1}\binom ni3^i3^{n(n-i)}+\sum_{i=1}^n\sum_{j=1}^n(-1)^{i+j+1}(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\) 后,化简则有

\[\begin{aligned}&{\color{white}{=}}\;\sum_{i=1}^n\sum_{j=1}^n(-1)^{i+j+1}(3\binom ni\binom nj3^{(n-i)(n-j)})\\&=3\sum_{i=1}^n((-1)^{i+1}\binom ni\sum_{j=1}^n((-1)^j\binom nj3^{n^2-ni-nj+ij}))\\&=3^{n^2+1}\sum_{i=1}^n((-1)^{i+1}\binom ni3^{-ni}\sum_{j=1}^n((-1)^j\binom nj3^{(i-n)j}))\\&=3^{n^2+1}\sum_{i=1}^n((-1)^{i+1}\binom ni3^{-ni}((\sum_{j=0}^n((-1)^j\binom nj3^{(i-n)j}))-1))\\&=3^{n^2+1}\sum_{i=1}^n((-1)^{i+1}\binom ni3^{-ni}((\sum_{j=0}^n(\binom nj(-3^{i-n})^j1^{n-j}))-1))\\&=3^{n^2+1}\sum_{i=1}^n((-1)^{i+1}\binom ni3^{-ni}((1-3^{i-n})^n-1))\end{aligned} \]

其中最后两步用到了二项式定理,即 \((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\) 的矩阵,可以构造矩阵如下:

\[\begin{bmatrix}A_{1,1}&A_{1,2}&\cdots&A_{1,n}&[1=1]&[1=2]&\cdots&[1=n]\\A_{2,1}&A_{2,2}&\cdots&A_{2,n}&[2=1]&[2=2]&\cdots&[2=n]\\\vdots&\vdots&\ddots&\vdots&\vdots&\vdots&\ddots&\vdots\\A_{n,1}&A_{n,2}&\cdots&A_{n,n}&[n=1]&[n=2]&\cdots&[n=n]\end{bmatrix} \]

将矩阵高斯消元,得到下面的矩阵:

\[\begin{bmatrix}[1=1]&[1=2]&\cdots&[1=n]&B_{1,1}&B_{1,2}&\cdots&B_{1,n}\\ [2=1]&[2=2]&\cdots&[2=n]&B_{2,1}&B_{2,2}&\cdots&B_{2,n}\\\vdots&\vdots&\ddots&\vdots&\vdots&\vdots&\ddots&\vdots\\ [n=1]&[n=2]&\cdots&[n=n]&B_{n,1}&B_{n,2}&\cdots&B_{n,n}\end{bmatrix} \]

此时 \(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\) 如下:

\[F=\begin{bmatrix}f(1)\\f(2)\\\vdots\\f(n)\end{bmatrix},G=\begin{bmatrix}g(1)\\g(2)\\\vdots\\g(n)\end{bmatrix} \]

若存在一个 \(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\)

二项式反演

内容

\[\begin{aligned}f(n)=\sum_{i=0}^n(-1)^i\binom nig(i)&\Leftrightarrow g(n)=\sum_{i=0}^n(-1)^i\binom nif(i)\cdots(1)\\f(n)=\sum_{i=n}^m(-1)^i\binom ing(i)&\Leftrightarrow g(n)=\sum_{i=n}^m(-1)^i\binom inf(i)\cdots(2)\\f(n)=\sum_{i=0}^n\binom nig(i)&\Leftrightarrow g(n)=\sum_{i=0}^n(-1)^{n-i}\binom nif(i)\cdots(3)\\f(n)=\sum_{i=n}^m\binom ing(i)&\Leftrightarrow g(n)=\sum_{i=n}^m(-1)^{i-n}\binom inf(i)\cdots(4)\end{aligned} \]

其中 \(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\) 即可。推式子可得如下:

\[\begin{aligned}(A\times A)_{i,j}&=\sum_{k=1}^{n+1}A_{i,k}A_{k,j}\\&=\sum_{k=1}^{n+1}\left((-1)^{k-1}\binom {i-1}{k-1}\times(-1)^{j-1}\binom {k-1}{j-1}\right)\\&=\sum_{k=j}^{i}\left((-1)^{k+j}\frac{(i-1)!}{(k-1)!(i-k)!}\times\frac{(k-1)!}{(j-1)!(k-j)!}\right)\\&=\sum_{k=j}^i\left((-1)^{k+j}\frac{(i-1)!(i-j)!}{(i-k)!(k-j)!(j-1)!(i-j)!}\right)\\&=\sum_{k=j}^i\left((-1)^{k-j}\binom {i-j}{k-j}\binom{i-1}{i-j}\right)\\&=\binom{i-1}{i-j}\sum_{k=0}^{i-j}\left(\binom{i-j}k(-1)^k1^{i-j-k}\right)\\&=[i\ge j]\binom{i-1}{i-j}(-1+1)^{i-j}\\&=[i\ge j][i=j]\binom{i-1}{i-j}\\&=[i=j]\binom{i-1}{0}\\&=[i=j]\end{aligned} \]

上式中第 \(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}\)。推式子可得如下:

\[\begin{aligned}(A\times B)_{i,j}&=\sum_{k=1}^{n+1}A_{i,k}B_{k,j}\\&=\sum_{k=1}^{n+1}\left((-1)^{k-j}\binom{i-1}{k-1}\binom{k-1}{j-1}\right)\\&=\sum_{k=j}^i\left((-1)^{k-j}\binom{i-j}{k-j}\binom{i-1}{i-j}\right)\\&=\binom{i-1}{i-j}\sum_{k=0}^{i-j}\left(\binom{i-j}k(-1)^k1^{i-j-k}\right)\\&=[i\ge j]\binom{i-1}{i-j}(-1+1)^{i-j}\\&=[i\ge j][i=j]\binom{i-1}{i-j}\\&=[i=j]\binom{i-1}0\\&=[i=j]\end{aligned} \]

\((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\) 个)则一般有:

\[\displaystyle g(i)=\sum_{j=i}^n\binom jif(j) \]

使用二项式反演简化计算即可。

注意:钦定 满足若干条件的元素中可能有重复的情况,和 至少 不同。

同时由于 \((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 TreeDistanceCF917D 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

posted @ 2022-10-04 15:34  Fran-Cen  阅读(119)  评论(0编辑  收藏  举报