【LG 4831】Scarlet loves WenHuaKe(生成函數)

題目連接ide

 一道好題,第一次用生成函數作題。感謝賽珂狼教我這個作法。函數

首先咱們顯然能夠把題目中的限制轉化成一個二分圖的模型:左邊有$n$個點,右邊有$m$個點,若是在棋盤$(i,j)$這個點上放了炮,那麼咱們把左邊第$i$個點向右邊第$j$個點連邊,那麼最終這個圖上左邊每一個點的度數都是$2$,右邊每一個點的度數都小於等於$2$。求合法圖的個數。
能夠發現,這個二分圖是由一些環和一些鏈組成的,每一條鏈都是屬於右邊的點數比屬於左邊的點數多一(咱們把屬於右邊的單獨的一個點也算做一條鏈)。而且這樣鏈的條數剛好爲$m-n$。
對於每條鏈或者每個環,一旦分配給它的點固定後,其內部的方案數就和外部無關了,只和該鏈(環)的大小有關。
咱們先計算有關於鏈的部分。spa

咱們定義函數$g_n$表示左邊有$n-1$個點,右邊有$n$個點的一條鏈內部排列的方案數。那就有:
$$g = <0, 1, 1, 6, \dots, \frac{n!(n-1)!}{2}>$$
簡述上述式子的由來,左邊$n-1$個點有排列$(n-1)!$,右邊$n$個點有排列$n!$,乘起來以後左右對稱要除以$2$。咱們構造一個生成函數把$g$掛上去,表示有一條鏈的狀況:
$$G(x) = \sum_{n} g_n \frac{x^n}{n!(n-1)!}$$
若是有兩條鏈咱們就把它本身卷一下,而且用$g^{[2]}$來表示新獲得的數列:
$$G^2(x) = \sum_{n} \left( \sum_{i = 0}^{n} \binom{n}{i} \binom{n - 2}{i - 1} g_i g_{n - i} \right) \frac{x^n}{n!(n-2)!}$$
$$G^2(x) =\sum_{n} g^{[2]}_n \frac{x^n}{n!(n-2)!}$$
注意到這個生成函數的形式和$G(x)$相比有些許變更,即在$x^n$下的係數有改動,這是由於在兩條鏈的時候右邊的點數會比左邊的點數多二,而不是一。因而更通常的,若是有$k$條鏈,就能夠寫成如下形式:
$$G^k(x) = \sum_{n} g^{[k]}_n \frac{x^n}{n!(n-k)!}$$
其中數列$g^{[m-n]}$就是咱們想要的有剛好$m-n$條鏈的時候的方案數。可是細心的讀者能夠發現這裏有一個小問題,就是咱們在作卷積的過程當中,咱們每卷一次就會增長一條鏈,這至關於枚舉了新加入的鏈的位置,因而在每個合法的狀況中,這$m-n$條鏈的加入順序不一樣會被算成不一樣的方案,因而最終咱們須要的應該是數列$\frac{g^{[m-n]}}{(m-n)!}$。
理解這一點很重要,咱們在計算環的時候一樣須要用到這一點,請你們自行揣摩。
咱們知道了$G(x)$,因而咱們只要求出$G^{m-n}(x)$就好了,直接快速冪能夠$O(mlog^2m)$,若是比較厲害寫$O(mlogm)$的多項式快速冪也是能夠的。3d

接下來咱們算環的部分。
咱們定義函數$f_n$表示左右邊都有$n$個點的環內部排列的方案數,那就有:
$$f = <0, 0, 1, 6, \dots ,\frac{(n!)^2}{2n}>$$
簡單說明上述式子的由來,左右邊分別有圓排列$(n-1)!$,合併起來有$n$中方案,同時鏡像對稱要除以$2$。同時,這裏有特殊狀況$f_1=0$,由於你不能有一個兩個點的環。這裏有讀者會發現,咱們定義了$f_0 = 0$,這是由於咱們接下來作卷積時必須保證每次選擇環的大小時不能用一個空環(也就是會致使實際環的個數會少一)。
一樣咱們構造了一個生成函數,表示一個環的狀況:
$$F(x) = \sum_{n} f_n \frac{x^n}{(n!)^2}$$
與鏈同理,若是咱們用了$k$個環,就有:
$$F^k(x) = \sum_{n} f^{[k]}_n \frac{x^n}{(n!)^2}$$
與鏈同理,每一種用了$k$個環的合法狀況會被重複計算到$k!$次,咱們要除掉它,同時咱們須要枚舉咱們用的環的個數$k$,因而咱們須要的是:
$$H(x) = \sum_{k} \frac{F^k(x)}{k!} = e^{F(x)}$$code

雖然我不會多項式$exp$,可是這裏的形式比較簡單咱們能夠手推。
咱們須要先求出$F(x)$的一個比較簡單的形式。根據$f$咱們能夠寫出:
$$F(x) = -\frac{x}{2} + \frac{1}{2} \sum_n \frac{x^n}{n} = -\frac{x}{2} - \frac{1}{2} ln(1 - x)$$blog

因而有:get

$$H(x) = \sum_n h_n \frac{x^n}{(n!)^2} = e^{F(x)} = e^{-\frac{1}{2} x} e^{-\frac{1}{2} ln(1-x)} = e^{-\frac{1}{2}x} (1-x)^{-\frac{1}{2}}$$it

前半部分用$e$直接展開:io

$$e^{-\frac{1}{2}x} = \sum_n (-\frac{1}{2})^n \frac{x^n}{n!}$$event

後半部分用廣義二項式展開:

$$(1-x)^{-\frac{1}{2}} = \sum_n \frac{(-\frac{1}{2})^{\underline{n}}}{n!} (-1)^n x^n$$

把這兩個捲起來就獲得了$H(x)$。

最後咱們須要把鏈和環拼起來,這一步作卷積或手動展開均可以了。

總複雜度是$O(mlogmlog(m-n))$,或者$O(mlogm)$。因而就和$m-n$無關了。

 

#include <cstdio>
#include <vector>
#include <algorithm>

using namespace std;

typedef vector<int> Poly;

const int N = (int)1e5 + 5;
const int MOD = 998244353;
const int _IV2 = (MOD - 1) / 2;

int n, m;

namespace {
  int fac[N], ifac[N];
  int Add(int a, int b) { return (a += b) >= MOD? a - MOD : a; }
  int Sub(int a, int b) { return (a -= b) < 0? a + MOD : a; }
  int Mul(int a, int b) { return (long long)a * b % MOD; }
  int Pow(int a, int b) {
    int r = 1;
    for (; b; b >>= 1, a = Mul(a, a)) if (b & 1) r = Mul(r, a);
    return r;
  }
  void Math_Init() {
    fac[0] = ifac[0] = 1;
    for (int i = 1; i < N; ++i) fac[i] = Mul(fac[i - 1], i);
    ifac[N - 1] = Pow(fac[N - 1], MOD - 2);
    for (int i = N - 2; ~i; --i) ifac[i] = Mul(ifac[i + 1], i + 1);
  }
}

namespace PO {
  int rev[(int)1e6 + 5];
  void Dft(Poly &a, int le) {
    for (int i = 0; i < le; ++i)
      if (i < rev[i]) swap(a[i], a[rev[i]]);
    for (int i = 1; i < le; i <<= 1) {
      int wn = Pow(3, (MOD - 1) / (i << 1));
      for (int j = 0; j < le; j += i << 1) {
        int w = 1, x, y;
        for (int k = 0; k < i; ++k, w = Mul(w, wn)) {
          x = a[j + k];
          y = Mul(a[j + k + i], w);
          a[j + k] = Add(x, y);
          a[j + k + i] = Sub(x, y);
        }
      }
    }
  }
  void Idft(Poly &a, int le) {
    reverse(a.begin() + 1, a.end());
    Dft(a, le);
    int iv = Pow(le, MOD - 2);
    for (int i = 0; i < le; ++i) a[i] = ::Mul(a[i], iv);
  }
  Poly Mul(Poly a, Poly b, int lim) {
    int n = a.size(), m = b.size(), l;
    for (l = 1; l < n + m - 1; l <<= 1);
    for (int i = 0; i < l; ++i)
      rev[i] = (rev[i >> 1] >> 1) | (i & 1? l >> 1 : 0);
    a.resize(l), Dft(a, l);
    b.resize(l), Dft(b, l);
    for (int i = 0; i < l; ++i) a[i] = ::Mul(a[i], b[i]);
    Idft(a, l), a.resize(lim);
    return a;
  }
  Poly Pow(Poly a, int b, int lim) {
    Poly r(lim);
    for (r[0] = 1; b; b >>= 1) {
      if (b & 1) r = Mul(r, a, lim);
      a = Mul(a, a, lim);
    }
    return r;
  }
}

int main() {
  Math_Init();
  scanf("%d%d", &n, &m);
  
  Poly F0(m + 1), F1(m + 1), fm(m + 1);
  for (int i = 0, dw = 1; i <= m; ++i) {
    F0[i] = Mul(dw, ifac[i]);
    if (i & 1) F0[i] = Sub(0, F0[i]);
    dw = Mul(dw, Sub(_IV2, i));
  }
  for (int i = 0, pw = 1; i <= m; ++i) {
    F1[i] = Mul(pw, ifac[i]);
    pw = Mul(pw, _IV2);
  }
  Poly expF = PO::Mul(F0, F1, m + 1);
  for (int i = 0; i <= m; ++i) {
    fm[i] = Mul(expF[i], Mul(fac[i], fac[i]));
  }

  Poly G(m + 1), gk(m + 1);
  G[1] = 1;
  for (int i = 2; i <= m; ++i) {
    G[i] = (MOD + 1) / 2;
  }
  G = PO::Pow(G, m - n, m + 1);
  for (int i = m - n; i <= m; ++i) {
    gk[i] = Mul(Mul(G[i], Mul(fac[i], fac[i - m + n])), ifac[m - n]);
  }

  int ans = 0;
  for (int i = 0; i <= n; ++i) {
    int ch1 = Mul(fac[m], Mul(ifac[i], ifac[m - i]));
    int ch2 = Mul(fac[n], Mul(ifac[i], ifac[n - i]));
    ans = Add(ans, Mul(Mul(fm[i], gk[m - i]), Mul(ch1, ch2)));
  }
  printf("%d\n", ans);
  return 0;
}
View Code

 

這裏簡單講一下爲何有$\sum_{n=1} \frac{x^n}{n} = -ln(1-x)$。

根據定義有:$ln'(x) = \frac{1}{x}$

根據導數的複合:$(ln(1-x))' = ln'(1-x)(1-x)' = \frac{-1}{1-x} = -\sum_n x^n$

對兩邊積分:$ln(1-x) = -\sum_{n=1} \frac{x^n}{n}$

相關文章
相關標籤/搜索