ICPC2018 Qingdao R L 题

发布时间 2023-12-15 08:33:33作者: zzafanti

传送门

description

\(n\) 个点 \(m\) 条边的简单无向图(可以不连通)满足加入非负整数条边后可以变成 \(n\) 个点的环的个数。

要求线性复杂度。

  • \(n\ge 3\)

solution

\(m>n\) 时,答案为 \(0\)

\(m=n\) 时,相当于问 \(n\) 个点构成一个环的方案数,为 \(\dfrac{(n-1)!}{2}\)

考虑当 \(m<n\) 的情况。

这时候的无向图一定是 \(k=n-m\) 条互不联通的链。我们不妨先把这些链看成是有序的,最后除以 \(k!\) 即可。

如果我们知道了每种大小的链有多少种方案,求出原答案就是一个有标号计数问题了,这让我们联想到指数生成函数。

\(g_{i}\) 表示大小(即点数)为 \(i\) 的有标号非空链的数量,有以下关系式:

  • \(g_{1}=1,g_{0}=0\)

  • \(g_{i}=\dfrac{i!}{2},i\ge 2\)

第一个式子是容易看出的,第二个式子是因为链不应该考虑正着放还是反着放,所以要阶乘除以 2。

我们写出 \(g\) 的指数生成函数 \(G(x)=\sum\limits_{i\ge 1} \dfrac{g_i}{i!} x^i=x+\dfrac{1}{2}x^2+\dfrac{1}{2}x^3\dots\)

把它推成封闭形式:

  • \(xG(x)=x^2+\dfrac{1}{2}x^3+\dfrac{1}{2}x^4+\dots\)

  • \((x-1)G(x)=\dfrac{1}{2}x^2-x\)

  • \(G(x)=\dfrac{x}{2}\cdot\dfrac{x-2}{x-1}\)

我们要求 \(k\) 条链拼起来,总点数为 \(n\) 的方案数,就是求 \([x^n]G(x)^k\)

来推 \(G(x)^k\)

乘积的几部分分别 \(k\) 次方,有 \(G(x)^k=\dfrac{x^k}{2^k}\cdot (\dfrac{1}{x-1})^k \cdot (x-2)^k\)

第一部分很好处理,第二部分 \((\dfrac{1}{x-1})^k=(1+x+x^2+x^3+\dots)^k\),其第 \(t\) 项就是把 \(t\) 分成 \(k\) 个有序非负整数的方案数,使用插板法可知方案数是 \(\dbinom{t+k-1}{k-1}\),最后一部分二项式展开就可以了。

于是我们可以写出 \([x^n]G(x)^k=\dfrac{1}{2^k}\sum\limits_{i=0}^{n-k}\dbinom{n-i-1}{k-1}\dbinom{k}{i}2^i(-1)^{k-i}\)

可以线性求出。

code

#include<bits/stdc++.h>
#define int long long
using namespace std;

using E=long long;
using ui=uint32_t;
using lint=__int128;
constexpr E inf=1e16,mod=1e9+7;

namespace Comb{
  vector<E> fac(1,1ll),ifac(1,1ll),INV(2,1ll),pw;

  inline E ksm(E a,E b,const E MOD=mod){
    E ret=1;
    assert(b>=0);
    while(b){
      if(b&1) ret=ret*a%MOD;
      a=a*a%MOD;
      b>>=1;
    }
    return ret;
  }
  int _n;
  void CombPrework(int n){
    pw.resize(n+1);
    pw[0]=1;
    for(int i=1; i<=n; i++) pw[i]=pw[i-1]*2%mod;
    _n=n;
    for(int i=2; i<=n; i++) INV.emplace_back((mod-mod/i)*INV[mod%i]%mod);
    fac.resize(n+1),ifac.resize(n+1);
    fac[0]=ifac[0]=1;
    for(int i=1; i<=n; i++) fac[i]=fac[i-1]*i%mod;
    ifac[n]=ksm(fac[n],mod-2);
    for(int i=n-1; i; i--) ifac[i]=ifac[i+1]*(i+1)%mod;
  }

  E Inv(E x){ return x<=_n?INV[x]:(x==1?1:Inv(mod%x)*(mod-mod/x)%mod); }

  inline E C(E n,E m){
    if(n<0||m<0||n<m) return 0;
    while(fac.size()<=n){
      fac.emplace_back(fac.back()*(fac.size())%mod);
      ifac.emplace_back(ifac.back()*ksm(ifac.size(),mod-2)%mod);
    }
    return fac[n]*ifac[m]%mod*ifac[n-m]%mod;
  }

  inline E A(E n,E m){
    if(m<0) return 0;
    return C(n,m)*fac[m]%mod;
  }
};
using namespace Comb;

void solve(){
  E n,m;
  cin>>n>>m;
  if(n==m){
    cout<<fac[n]*ksm(2*n,mod-2)%mod<<'\n';
    return ;
  }
  E k=n-m;
  if(k<0){
    cout<<0<<'\n';
    return ;
  }
  E ans=0;
  for(E i=0; i<=m; i++){
    E x=C(m-i+k-1,k-1)*C(k,i)%mod;
    if(k-i>=0) x=x*pw[k-i]%mod;
    else x=0;
    if(i&1) ans=(ans-x+mod)%mod;
    else ans=(ans+x)%mod;
  }
  ans=ans*ksm(pw[k],mod-2)%mod*fac[n]%mod*ifac[k]%mod;
  cout<<(ans%mod+mod)%mod<<'\n';
}

signed main(){

  cout.tie(nullptr),cin.tie(nullptr)->sync_with_stdio(false);

  CombPrework(5e5);
  int T;
  cin>>T;
  while(T--) solve();

  return 0;
}