数数题整理

组合数

定义:\(n\) 个不同的元素里选择 \(m\) 个的方案数,记作 \(n\choose m\)

性质:

\[\begin{cases} \displaystyle{n+m\choose n}={n+m\choose m}\\ \displaystyle{n\choose m}={n-1\choose m-1}+{n-1\choose m}\\ \displaystyle{n+r+1\choose r}={n+r\choose r}+{n+r-1\choose r-1}+\cdots+{n\choose 0}\\ \displaystyle{n\choose l}{l\choose r}={n\choose r}{n-r\choose l-r}\\ \displaystyle{n\choose 0}+{n\choose 1}+\cdots+{n\choose n}=2^n\\ \displaystyle{n\choose 0}-{n\choose 1}+{n\choose 2}-\cdots=0\\ \displaystyle{n+1\choose r+1}={r\choose r}+{r+1\choose r}+\cdots+{n\choose r}\\ \end{cases} \]

二项式定理:

\[(a+b)^n=\sum_{i=0}^n{n\choose i}a^ib^{n-i} \]

容斥原理

\[\begin{aligned} A_i&=\{x:x\text{ has porperty } P_i\}\\ \left|\bigcup_{i=1}^n A_i\right|&=\sum_{1\le i\le n}|A_i|-\sum_{1\le i\lt j\le n}|A_i\cap A_j|+\sum_{1\le i\lt j\lt k\le n}|A_i\cup A_j\cup A_k|-\cdots+(-1)^{n-1}|A_1\cap A_2\cap\cdots\cap A_n| \end{aligned} \]

有时容斥系数会是莫比乌斯函数 \(\mu(n)\)

子集反演

两种形式:

\[\begin{cases} \displaystyle g(S)=\sum_{T\subseteq S}f(T)\Leftrightarrow f(S)=\sum_{T\subseteq S}(-1)^{|S|-|T|}g(T)\\ \displaystyle g(S)=\sum_{S\subseteq T}f(T)\Leftrightarrow f(S)=\sum_{S\subseteq T}(-1)^{|T|-|S|}g(T) \end{cases} \]

构造 \(f,g\) 时的要求:\(f\) 能知道答案,\(g\) 比较好求。

入手点一般是题目中对答案的限制条件。

二项式反演

三种形式:

\[\begin{cases} \displaystyle g(n)=\sum_{i=0}^n{n\choose i}f(i)\Leftrightarrow f(n)=\sum_{i=0}^n(-1)^{n-i}{n\choose i}g(i)\\ \displaystyle g(n)=\sum_{i=0}^n(-1)^i{n\choose i}f(i)\Leftrightarrow f(n)=\sum_{i=0}^n(-1)^i{n\choose i}g(i)\\ \displaystyle g(k)=\sum_{i=k}^n{i\choose k}f(i)\Leftrightarrow f(k)=\sum_{i=k}^n(-1)^{i-k}{i\choose k}g(i) \end{cases} \]

直接求不好求,但是至少好求;直接求不好求,但是至多好求。

构造 \(f,g\) 时的要求:\(f\) 能知道答案,\(g\) 比较好求。

一般来说,\(f(i)=\text{exactly }i\)\(g(i)=\text{at least/most }i\)

小球盒子模型(八重计数法)

以下用 \(n\) 表示小球个数,\(m\) 表示盒子个数。

  1. 球相同,盒子不同,不能有空盒:\({n-1\choose m-1}\)(插板)。
  2. 球相同,盒子不同,可以有空盒:\({n+m-1\choose m-1}\)(先给每个盒子放一个球,然后插板)。
  3. 球不同,盒子不同,可以有空盒:\(m^n\)(每个球 \(m\) 种选择)。
  4. 球不同,盒子相同,不能有空盒:\(n\brace m\)(第二类斯特林数的定义)。
  5. 球不同,盒子不同,不能有空盒:\(m!{n\brace m}\)
  6. 球不同,盒子相同,可以有空盒:\(\sum_{i=1}^n{n\brace i}\)(贝尔数)。
  7. 球相同,盒子相同,可以有空盒:\(f_{n,k}=f_{n,k-1}+f_{n-k,k}\)
  8. 球相同,盒子相同,不能有空盒:\(f_{n-k,k}\)(先给每个盒子放一个球,然后随便放)。

斯特林数

第一类斯特林数

定义:将 \(n\) 个两两不同的元素,划分为 \(k\) 个互不区分的非空轮换的方案数,记作 \(n\brack m\)

递推式:

\[{n\brack m}=(n-1)\cdot{n-1\brack m}+{n-1\brack m-1} \]

证明与第二类斯特林数的式子类似。

没有实用的通项公式。

性质:

\[\sum_{i=0}^n{n\brack i}=n! \]

第二类斯特林数

定义:将 \(n\) 个不同的球放在 \(m\) 个相同的盒子里(盒子不能为空)的方案数,记作 \(n\brace m\)

递推式:

\[{n\brace m}=m\cdot {n-1\brace m}+{n-1\brace m-1} \]

证明可以考虑组合意义:如果第 \(n\) 个球放到一个新的盒子里,那就是 \(n-1\brace m-1\),否则先 \(n-1\brace m\),再考虑第 \(n\) 个球放在哪个盒子里,于是就要再乘 \(m\)。证毕。

通项公式:

\[{n\brace m}=\frac 1{m!}\sum_{i=0}^m(-1)^{m-i}{m\choose i}i^n \]

证明:设 \(f(i)\) 表示将 \(n\) 个不同的球放在 \(i\) 个不同的盒子里(盒子不能为空)的方案数,设 \(g(i)\) 表示将 \(n\) 个不同的球放在 \(i\) 个不同的盒子里(盒子可以为空)的方案数。则 \(g(i)=i^n,f(i)=i!{n\brace i}\)

则有:

\[g(m)=\sum_{i=0}^m{m\choose i}f(i) \]

由二项式反演,得:

\[f(m)=\sum_{i=0}^m(-1)^{m-i}{m\choose i}g(i)=\sum_{i=0}^m(-1)^{m-i}{m\choose i}i^n \]

证毕。

上升幂与下降幂

定义:

\[\begin{cases} \displaystyle x^{\overline n}=\prod_{i=0}^{n-1}(x+i)\\ \displaystyle x^{\underline n}=\prod_{i=0}^{n-1}(x-i) \end{cases} \]

下降幂转组合数:

\[x^{\underline n}={x\choose n}n! \]

普通幂转上升幂、下降幂:

\[\begin{cases} \displaystyle x^n=\sum_{i=0}^n{n\brace i}x^{\underline i}\\ \displaystyle x^n=\sum_{i=0}^n(-1)^{n-i}{n\brace i}x^{\overline i} \end{cases} \]

上升幂、下降幂转普通幂:

\[\begin{cases} \displaystyle x^{\underline n}=\sum_{i=0}^n(-1)^{n-i}{n\brack i}x^i\\ \displaystyle x^{\overline n}=\sum_{i=0}^n{n\brack i}x^i \end{cases} \]

斯特林反演

\[f(n)=\sum_{i=0}^n{n\brace i}g(i)\Leftrightarrow g(n)=\sum_{i=0}^n(-1)^{n-i}{n\brack i}f(i) \]

Burnside 引理和 Polya 定理

\[ans=\frac{1}{|G|}\sum_{g\in G}f(g)=\frac{1}{|G|}\sum_{g\in G}m^{c(g)} \]

\(G\) 为置换群,\(f(g)\) 为在置换 \(g\) 作用下仍不变的方案数,\(m\) 为颜色数,\(c(g)\) 为置换 \(g\) 能拆出来的循环数。

习题

UVA10325 The Lottery

补集转化:将都不能被 \(a_i\) 整除转化为全集减能被 \(a_i\) 整除,然后用容斥原理解决。

\(A_i=\{x:1\le x\le n,a_i\mid x\}\)

Code
#include<algorithm>
#include<cstdio>

typedef long long ll;

int n,m;
ll a[30];

ll lcm(ll a,ll b){
  return a/std::__gcd(a,b)*b;
}

bool mian(){
  if(scanf("%d%d",&n,&m)==EOF)return 0;
  for(int i=1;i<=m;i++)
    scanf("%lld",a+i);
  ll ans=n;
  for(int i=1;i<=(1<<m)-1;i++){
    ll _lcm=1;
    for(int j=0;j<m;j++)
      if((i>>j)&1){
        _lcm=lcm(_lcm,a[j+1]);
        if(_lcm>n)break;
      }
    if(__builtin_popcountll(i)%2==1)ans-=n/_lcm;
    else ans+=n/_lcm;
  }
  printf("%lld\n",ans);
  return 1;
}

int main(){
  while(mian());
  return 0;
}

UVA11806 Cheerleaders

补集转化:将四条边上都有人转化为全集有边上没人,然后用容斥原理解决。

\(A_i=\{x:\text{arrangement }x\text{ that satisfies there are }i\text{ sides which don't have person}\}\)

\(|A_i|\) 时枚举有哪些边上没人,那么相应的能放人的地方是一个矩形。然后用组合数计算即可。

Code
#include<algorithm>
#include<cstdio>

const int N=500;
const int P=int(1e6)+7;

int C[N+10][N+10];

void init(){
  C[0][0]=1;
  for(int i=1;i<=N;i++){
    C[i][0]=1;
    for(int j=1;j<=i;j++)
      C[i][j]=(C[i-1][j]+C[i-1][j-1])%P;
  }
}

void mian(int tc){
  int n,m,k;scanf("%d%d%d",&n,&m,&k);
  int ans=0;
  for(int msk=0;msk<=15;msk++){ // msk 表示哪些边上放人了
    int x=n,y=m;
    for(int i=0;i<4;i++)
      if((msk>>i)&1){
        if(i%2==0)x--;
        else y--;
      }
    if(__builtin_popcount(msk)%2==0)
      (ans+=C[x*y][k])%=P;
    else (ans-=C[x*y][k])%=P;
  }
  printf("Case %d: %d\n",tc,(ans+P)%P);
}

int main(){
  init();
  int T;scanf("%d",&T);
  for(int i=1;i<=T;i++)mian(i);
  return 0;
}

SP6285 NGM2 - Another Game With Numbers

UVA10325 双倍经验。

SP4168 SQFREE - Square-free integers

没看出这个题怎么用容斥做。。

结论:\(\mu^2(i)=\sum_{d^2\mid i}\mu(d)\)。证明:

\(i\) 的因数中,最大的平方数是 \(q=p_1^{2\alpha_1}p_2^{2\alpha_2}\cdots p_k^{2\alpha_k}\),则 \(d^2\mid i\Leftrightarrow d\mid\sqrt q\)

\[\sum_{d^2\mid i}\mu(d)=\sum_{d\mid\sqrt q}\mu(d)=[\sqrt q=1]=[q=1] \]

\([q=1]\) 相当于 \(i\) 没有平方因数,也就是 \(\mu^2(i)=1\)。证毕。

于是我们来推式子:

\[\sum_{i=1}^n\mu^2(i)=\sum_{i=1}^n\sum_{d^2\mid i}\mu(d)=\sum_{d=1}^{\lfloor\sqrt n\rfloor}\mu(d)\sum_{i=1}^n[d^2\mid i]=\sum_{d=1}^{\lfloor\sqrt n\rfloor}\mu(d)\left\lfloor\frac n{d^2}\right\rfloor \]

整除分块即可。时间复杂度 \(O(\sqrt[3]n)\),证明:

复杂度即为不同的 \(\left\lfloor\frac n{d^2}\right\rfloor\) 的个数。

\(d\le\sqrt[3]n\) 时,显然最多有 \(\sqrt[3]{n}\) 个数。

\(d\gt\sqrt[3]n\) 时,\(\left\lfloor\frac n{d^2}\right\rfloor\lt\left\lfloor\frac n{(\sqrt[3]n)^2}\right\rfloor=\left\lfloor \sqrt[3]n\right\rfloor\),最多 \(\sqrt[3]n\) 个数。

综上,最多有 \(2\sqrt[3]n\) 个数。证毕。

Code
#include<algorithm>
#include<cstdio>
#include<cmath>

typedef long long ll;

const int N=1e7;

int prm[N+10],notPrm[N+10],totp,mu[N+10],smu[N+10];

void sieve(){
  notPrm[1]=1,mu[1]=1;
  for(int i=2;i<=N;i++){
    if(!notPrm[i])prm[++totp]=i,mu[i]=-1;
    for(int j=1;j<=totp&&i*prm[j]<=N;j++){
      notPrm[i*prm[j]]=1;
      if(i%prm[j]==0)break;
      mu[i*prm[j]]=-mu[i];
    }
  }
  for(int i=1;i<=N;i++)
    smu[i]=smu[i-1]+mu[i];
}

void mian(){
  ll n;scanf("%lld",&n);
  ll ans=0;
  for(ll l=1,r;l<=ll(sqrt(n));l=r+1){
    r=std::min(ll(sqrt(n/(n/(l*l)))),ll(sqrt(n)));
    ans+=1LL*(n/(l*l))*(smu[r]-smu[l-1]);
  }
  printf("%lld\n",ans);
}

int main(){
  sieve();
  int T;scanf("%d",&T);
  while(T--)mian();
  return 0;
}

P4318 完全平方数

二分答案之后就和上一个题一模一样。

Code
#include<algorithm>
#include<cstdio>
#include<cmath>

typedef long long ll;

const int N=1e7;

int prm[N+10],notPrm[N+10],totp,mu[N+10],smu[N+10];
ll k;

void sieve(){
  notPrm[1]=1,mu[1]=1;
  for(int i=2;i<=N;i++){
    if(!notPrm[i])prm[++totp]=i,mu[i]=-1;
    for(int j=1;j<=totp&&i*prm[j]<=N;j++){
      notPrm[i*prm[j]]=1;
      if(i%prm[j]==0)break;
      mu[i*prm[j]]=-mu[i];
    }
  }
  for(int i=1;i<=N;i++)
    smu[i]=smu[i-1]+mu[i];
}

bool check(ll x){
  ll ans=0;
  for(ll l=1,r;l<=ll(sqrt(x));l=r+1){
    r=std::min(ll(sqrt(x/(x/(l*l)))),ll(sqrt(x)));
    ans+=1LL*(x/(l*l))*(smu[r]-smu[l-1]);
  }
  return ans>=k;
}

void mian(){
  scanf("%lld",&k);
  ll l=1,r=10000000000LL,mid,ans=-1;
  while(l<=r){
    mid=(l+r)>>1;
    if(check(mid))ans=mid,r=mid-1;
    else l=mid+1;
  }
  printf("%lld\n",ans);
}

int main(){
  sieve();
  int T;scanf("%d",&T);
  while(T--)mian();
  return 0;
}

P3349 [ZJOI2016] 小星星

考虑暴力 dp:设 \(f_{u,x,S}\) 表示现在饰品中的 \(u\) 映射到了 \(x\),以 \(u\) 为根的子树中用掉了的映射的集合为 \(S\) 时的答案。

(因为我们要做树形 dp,所以要设状态 \(u\),因为映射到的数不能重复,所以要设 \(x,S\)

转移方程如下(\(E\) 表示原来饰品的边集):

\[f_{u,x,S}=\prod_{v\in\operatorname{son}(u)}\sum_{T\subseteq S}\sum_{y\in T,(x,y)\in E}f_{v,y,T} \]

由于要枚举子集,时间复杂度为 \(\mathcal O(n^33^n)\),过不了。

把枚举子集改成容斥:先枚举 \(S\),然后钦定我们只能映射到集合 \(S\) 里的数,但是映射到的数可以重复。然后通过容斥解决答案会被算多的问题。

这样 dp 状态就可以去掉一维了:

\[f_{u,x}=\prod_{v\in\operatorname{son}(u)}\sum_{y\in S,(x,y)\in E}f_{v,y} \]

时间复杂度降到了 \(\mathcal O(n^32^n)\),可以通过。

Code
#include<algorithm>
#include<cstdio>
#include<cstring>

typedef long long ll;

const int N=17;

int n,m,G[N+10][N+10],T[N+10][N+10];
ll f[N+10][N+10],ans;

void DFS(int u,int _fa,int msk){
  for(int x=1;x<=n;x++)
    if((msk>>(x-1))&1)
      f[u][x]=1;
  for(int v=1;v<=n;v++)
    if(v!=_fa&&T[u][v]){
      DFS(v,u,msk);
      for(int x=1;x<=n;x++)
        if((msk>>(x-1))&1){
          ll sum=0;
          for(int y=1;y<=n;y++)
            if((msk>>(y-1))&1&&G[x][y])
              sum+=f[v][y];
          f[u][x]*=sum;
        }
    }
}

int main(){
  scanf("%d%d",&n,&m);
  for(int i=1;i<=m;i++){
    int u,v;scanf("%d%d",&u,&v);
    G[u][v]=G[v][u]=1;
  }
  for(int i=1;i<n;i++){
    int u,v;scanf("%d%d",&u,&v);
    T[u][v]=T[v][u]=1;
  }
  for(int msk=0;msk<=(1<<n)-1;msk++){
    memset(f,0,sizeof(f));
    DFS(1,0,msk);
    ll res=0;
    for(int x=1;x<=n;x++)
      if((msk>>(x-1))&1)res+=f[1][x];
    ans+=((n-__builtin_popcount(msk))&1)?-res:res;
  }
  printf("%lld\n",ans);
  return 0;
}

SP9097 NOVICE65 - Derangements HARD

考虑对 \(\forall 1\le i\le n,a_i\ne b_i\) 这个限制条件运用子集反演:

\(f(S)\) 表示 \(\forall i\in S,a_i=b_i\)\(\forall i\notin S,a_i\ne b_i\) 时的答案,显然 \(ans=f(\varnothing)\)

\(g(S)\) 表示 \(\forall i\in S,a_i=b_i\) 时的答案。

\[g(S)=\sum_{S\subseteq T}f(T)\Leftrightarrow f(S)=\sum_{S\subseteq T}(-1)^{|T|-|S|}g(T) \]

计算 \(g(S)\) 时相当于一个可重排列,答案即(\(cnt_i\) 表示 \(i\)\(S\) 外的出现次数):

\[\dfrac{(n-|S|)!}{\prod_{i=0}^{n-1}cnt_i!} \]

Code
#include<algorithm>
#include<cstdio>
#include<cstring>

typedef long long ll;

const int N=15;

int n,a[N+10],cnt[N+10];
ll fac[N+10];

void mian(){
  scanf("%d",&n);
  for(int i=1;i<=n;i++)
    scanf("%d",a+i);
  ll ans=0;
  for(int msk=0;msk<=(1<<n)-1;msk++){
    ll res=0;
    memset(cnt,0,sizeof(cnt));
    for(int i=1;i<=n;i++)
      if(!((msk>>(i-1))&1))cnt[a[i]]++;
    res=fac[n-__builtin_popcount(msk)];
    for(int i=0;i<n;i++)res/=fac[cnt[i]];
    ans+=(((__builtin_popcount(msk))&1)?-1:1)*res;
  }
  printf("%lld\n",ans);
}

int main(){
  fac[0]=1;
  for(int i=1;i<=N;i++)fac[i]=fac[i-1]*i;
  int T;scanf("%d",&T);
  while(T--)mian();
  return 0;
}

P3298 [SDOI2013] 泉

如果直接做,则由于我们不好判断这 \(k\) 个以外的东西是否相等,所以没法做。

恰好 \(k\) 个满足条件不好做,但至少 \(k\) 个满足条件是可以做的:

我们枚举所有满足 \(S\subseteq\{1,2,3,4,5,6\},|S|=k\) 的集合 \(S\),然后钦定所有以 \(S\) 内元素 \(j\) 为下标的 \(a_{i,j}\) 必须一样,其它随便。判断这 \(k\) 个元素是否相等可以通过 hash \(a\) 数组实现。将所有 \(S\) 的答案加起来就是我们想要的。

于是设恰好 \(k\) 个时答案为 \(f(k)\),至少 \(k\) 个时答案为 \(g(k)\)

\[g(k)=\sum_{i=k}^6{i\choose k}f(i)\Leftrightarrow f(k)=\sum_{i=k}^6(-1)^{i-k}{i\choose k}g(i) \]

Code
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<utility>

typedef long long ll;

const int N=1e5,M=6;
const int P1=1212121,P2=19260817,B1=101,B2=233;

int n,k,a[N+10][M+10];
std::pair<int,int> hsh[N+10];
ll g[1<<M];
int fac[M+10];

ll calc(int msk){
  if(msk==0)return 1LL*n*(n-1)/2;
  memset(hsh,0,sizeof(hsh));
  for(int i=1;i<=M;i++)
    if((msk>>(i-1))&1)
      for(int j=1;j<=n;j++){
        hsh[j].first =(1LL*hsh[j].first *B1%P1+a[j][i])%P1;
        hsh[j].second=(1LL*hsh[j].second*B2%P2+a[j][i])%P2;
      }
  std::sort(hsh+1,hsh+n+1);
  ll res=0;
  for(int i=1,j=1;i<=n;i=j,j=i){
    while(hsh[j]==hsh[i])j++;
    res+=1LL*(j-i)*(j-i-1)/2;
  }
  return res;
}

int main(){
  fac[0]=1;
  for(int i=1;i<=M;i++)fac[i]=fac[i-1]*i;
  scanf("%d%d",&n,&k);
  for(int i=1;i<=n;i++)
    for(int j=1;j<=M;j++)
      scanf("%d",&a[i][j]);
  for(int msk=0;msk<=(1<<M)-1;msk++)
    g[__builtin_popcount(msk)]+=calc(msk);
  ll ans=0;
  for(int i=k;i<=M;i++)
    ans+=(((i-k)&1)?-1LL:1LL)*(fac[i]/fac[k]/fac[i-k])*g[i];
  printf("%lld\n",ans);
  return 0;
}

P5505 [JSOI2011] 分特产

我们可以一个一个特产的分,但是每个人不一定要拿到所有特产,所以没法这样。

所有人都有特产不好做,但是至少 \(k\) 个人特产是可以做的:

先选 \(k\) 个人,钦定他们没特产,剩下的随便。那么对于每一个特产,它就是一个小球盒子模型:

\[g(k)={n\choose k}\prod_{i=1}^n{a_i+n-i-1\choose n-i-1} \]

\(f(i)\) 为恰好 \(i\) 个人没有特产的答案,\(g(i)\) 为至少 \(i\) 个人没有特产的答案,那么答案就是 \(f(0)\)

\[g(k)=\sum_{i=k}^n{i\choose k}f(i)\Leftrightarrow f(k)=\sum_{i=k}^n(-1)^{i-k}{i\choose k}g(i) \]

Code
#include<algorithm>
#include<cstdio>

const int N=2e3;
const int P=1e9+7;

int n,m,a[N+10];
int C[N+10][N+10];

void initC(){
  C[0][0]=1;
  for(int i=1;i<=N;i++){
    C[i][0]=1;
    for(int j=1;j<=i;j++)
      C[i][j]=(C[i-1][j-1]+C[i-1][j])%P;
  }
}

int calc(int x){
  int res=1;
  for(int i=1;i<=m;i++)
    res=1LL*res*C[a[i]+n-x-1][n-x-1]%P;
  return 1LL*res*C[n][x]%P;
}

int main(){
  initC();
  scanf("%d%d",&n,&m);
  for(int i=1;i<=m;i++)
    scanf("%d",a+i);
  int ans=0;
  for(int i=0;i<n;i++)
    (ans+=((i&1)?-1LL:1LL)*calc(i)%P)%=P;
  printf("%d\n",(ans+P)%P);
  return 0;
}

CF1342E Placing Rooks

因为所有格子都要被攻击到,所以每行必须有一个棋子或者每列都必须有一个棋子。

除了 \(k=0\) 时(此时答案即 \(n!\)),以上两种情况互不重合且等价。我们现在只考虑每行都有棋子的情况。

首先恰好 \(k\) 列没有棋子,这个读者自证不难。

然后这 \(n\) 不同行的棋子要放在 \(n-k\) 个不同列中,方案数是 \((n-k)!{n\brace n-k}\)

所以总的方案数是:

\[{n\choose k}(n-k)!{n\brace n-k} \]

那个第二类斯特林数可以用通项公式求。

Code
#include<algorithm>
#include<cstdio>

typedef long long ll;

const int N=2e5;
const int P=998244353;

int fac[N+10],ifac[N+10];

int qpow(int a,int b,int p=P){
  int res=1;
  for(;b;a=1LL*a*a%p,b>>=1)
    if(b&1)res=1LL*res*a%p;
  return res;
}

void initC(){
  fac[0]=1;
  for(int i=1;i<=N;i++)fac[i]=1LL*fac[i-1]*i%P;
  ifac[N]=qpow(fac[N],P-2);
  for(int i=N-1;i>=0;i--)ifac[i]=1LL*ifac[i+1]*(i+1)%P;
}

int C(int a,int b){
  return 1LL*fac[a]*ifac[a-b]%P*ifac[b]%P;
}

int S(int n,int k){
  int res=0;
  for(int i=0;i<=k;i++)
    (res+=((i&1)?-1LL:1LL)*C(k,i)*qpow(k-i,n)%P)%=P;
  return 1LL*res*ifac[k]%P;
}

int main(){
  initC();
  int n;ll k;
  scanf("%d%lld",&n,&k);
  if(k>=n)return puts("0"),0;
  if(k==0)return printf("%d\n",fac[n]),0;
  printf("%d\n",(2LL*C(n,k)*fac[n-k]%P*S(n,n-k)%P+P)%P);
  return 0;
}

CF932E Team Work

我们来推式子:

\[\begin{aligned} &\sum_{i=0}^n{n\choose i}i^k\\ =&\sum_{i=0}^n{n\choose i}\sum_{j=0}^k{k\brace j}i^{\underline j}\\ =&\sum_{j=0}^k{k\brace j}\sum_{i=0}^n{n\choose i}{i\choose j}j!\\ =&\sum_{j=0}^k{k\brace j}j!\sum_{i=0}^{n-j}{n\choose j}{n-j\choose i-j}\\ =&\sum_{j=0}^k{k\brace j}j!{n\choose j}2^{n-j}\\ =&\sum_{j=0}^k{k\brace j}n^{\underline j}2^{n-j} \end{aligned} \]

\({k\brace j}\) 可以 \(\mathcal O(k^2)\) 求,于是这个题就做完了。

Code
#include<algorithm>
#include<cstdio>

const int N=5000;
const int P=int(1e9)+7;

int n,k,S[N+10][N+10];
int ans;

void init(){
  S[0][0]=1;
  for(int i=1;i<=N;i++)
    for(int j=1;j<=N;j++)
      S[i][j]=(S[i-1][j-1]+1LL*S[i-1][j]*j%P)%P;
}

int qpow(int a,int b){
  int res=1;
  for(;b;b>>=1,a=1LL*a*a%P)
    if(b&1)res=1LL*res*a%P;
  return res;
}

int main(){
  init();
  scanf("%d%d",&n,&k);
  int Cni=1;
  for(int i=0;i<=std::min(n,k);i++){
    (ans+=1LL*S[k][i]%P*Cni%P*qpow(2,n-i)%P)%=P;
    Cni=1LL*Cni*(n-i)%P;
  }
  printf("%d\n",ans);
  return 0;
}

CF1278F Cards

首先答案就是 \(\sum_ii^k\ \times\) 恰好 \(i\) 次是王牌的概率。

\(p=\frac 1m,q=1-p\),则答案为:

\[\begin{aligned} &\sum_{i=0}^n{n\choose i}p^iq^{n-i}i^k\\ =&\sum_{i=0}^n{n\choose i}p^iq^{n-i}\sum_{j=1}^k{k\brace j}i^{\underline j}\\ =&\sum_{i=0}^n{n\choose i}p^iq^{n-i}\sum_{j=1}^k{k\brace j}{i\choose j}j!\\ =&\sum_{j=1}^k{k\brace j}j!\sum_{i=0}^n{n\choose i}{i\choose j}p^iq^{n-i}\\ =&\sum_{j=1}^k{k\brace j}j!{n\choose j}\sum_{i=0}^n{n-j\choose i-j}p^iq^{n-i}\\ =&\sum_{j=1}^k{k\brace j}n^{\underline j}\sum_{t=0}^{n-j}{n-j\choose t}p^{t+j}q^{n-j-t}\quad(\text{let }t=i-j)\\ =&\sum_{j=1}^k{k\brace j}n^{\underline j}p^j\sum_{t=0}^{n-j}{n-j\choose t}p^tq^{n-j-t}\\ =&\sum_{j=1}^k{k\brace j}n^{\underline j}p^j(p+q)^{n-j}\\ =&\sum_{j=1}^k{k\brace j}n^{\underline j}p^j \end{aligned} \]

(倒数第二步是用了二项式定理)

于是就做完了。

Code
#include<algorithm>
#include<cstdio>

const int N=5000;
const int P=998244353;

int n,m,k,S[N+10][N+10];
int ans;

void init(){
  S[0][0]=1;
  for(int i=1;i<=N;i++)
    for(int j=1;j<=N;j++)
      S[i][j]=(S[i-1][j-1]+1LL*S[i-1][j]*j%P)%P;
}

int qpow(int a,int b){
  int res=1;
  for(;b;b>>=1,a=1LL*a*a%P)
    if(b&1)res=1LL*res*a%P;
  return res;
}

int main(){
  init();
  scanf("%d%d%d",&n,&m,&k);
  int Cni=1,inv=qpow(m,P-2);
  for(int i=0;i<=std::min(n,k);i++){
    (ans+=1LL*S[k][i]%P*Cni%P*qpow(inv,i)%P)%=P;
    Cni=1LL*Cni*(n-i)%P;
  }
  printf("%d\n",ans);
  return 0;
}

P6620 [省选联考 2020 A 卷] 组合数问题

zhx yyds!!

\[\begin{aligned} &\sum_{k=0}^nf(k)x^k{n\choose k}\\ =&\sum_{k=0}^n\sum_{i=0}^ma_ik^ix^k{n\choose k}\\ =&\sum_{k=0}^n\sum_{i=0}^mb_ik^{\underline i}x^k{n\choose k}\\ =&\sum_{k=0}^nx^k\sum_{i=0}^mb_ii!{k\choose i}{n\choose k}\\ =&\sum_{k=0}^nx^k\sum_{i=0}^mb_ii!{n\choose i}{n-i\choose k-i}\\ =&\sum_{i=0}^mb_in^{\underline i}\sum_{k=0}^nx^k{n-i\choose k-i}\\ =&\sum_{i=0}^mb_in^{\underline i}\sum_{t=0}^{n-i}x^{i+t}{n-i\choose t}\quad(\text{let }t=i+k)\\ =&\sum_{i=0}^mb_in^{\underline i}x^i\sum_{t=0}^{n-i}{n-i\choose t}x^t1^{n-i-t}\\ =&\sum_{i=0}^mb_in^{\underline i}x^i(x+1)^{n-i} \end{aligned} \]

\(b_i\)\(f(k)\) 转成下降幂多项式后的系数:

\[\begin{aligned} &\sum_{i=0}^ma_ix^i\\ =&\sum_{i=0}^ma_i\sum_{j=1}^i{i\brace j}x^{\underline j}\\ =&\sum_{j=0}^mx^{\underline j}\sum_{i=j}^ma_i{i\brace j}\\ \therefore&\ b_i=\sum_{j=i}^ma_j{j\brace i} \end{aligned} \]

于是 \(b\) 就可以 \(\mathcal O(m^2)\) 预处理了。

Code
#include<algorithm>
#include<cstdio>

const int M=1000;

int n,x,p,m,a[M+10],b[M+10];
int S[M+10][M+10];

void init(){
  S[0][0]=1;
  for(int i=1;i<=M;i++)
    for(int j=1;j<=M;j++)
      S[i][j]=(S[i-1][j-1]+1LL*S[i-1][j]*j%p)%p;
}

int qpow(int a,int b){
  int res=1;
  for(;b;a=1LL*a*a%p,b>>=1)
    if(b&1)res=1LL*res*a%p;
  return res;
}

int main(){
  scanf("%d%d%d%d",&n,&x,&p,&m);
  for(int i=0;i<=m;i++)
    scanf("%d",a+i);
  init();
  for(int i=0;i<=m;i++)
    for(int j=i;j<=m;j++)
      (b[i]+=1LL*a[j]*S[j][i]%p)%=p;
  int ans=0,fac=1;
  for(int i=0;i<=m;i++){
    (ans+=1LL*b[i]*fac%p*qpow(x,i)%p*qpow(x+1,n-i)%p)%=p;
    fac=1LL*fac*(n-i)%p;
  }
  printf("%d\n",ans);
  return 0;
}
posted @ 2022-02-10 20:04  registerGen  阅读(113)  评论(0)    收藏  举报