kazuma8128’s blog

競プロの面白い問題を解きます

色々な畳み込み

最近覚えたのでメモ.
理論とか仕組みとかの説明はしません.

高速フーリエ変換(FFT)

添え字和での畳み込み

  • Σ(i + j = k) a_i * b_j = c_k

この形で長さ n の列 a, b から長さ 2n の列 c を求めます. ただし n は2の冪乗とします.

列 a, b を離散フーリエ変換して, かけ合わせたものを逆離散フーリエ変換すると c が求まります.
計算量は O(nlogn) です.

コード

長いのでリンクだけ
github.com

ちなみに整数での特殊な mod の場合は 高速剰余変換(NTT) が使えて定数倍高速かつ誤差なく計算できます.

高速ゼータ変換(FZT)

上位/下位集合の畳み込み

  • Σ(j ⊂ i) a_i = b_j

または

  • Σ(i ⊂ j) a_i = b_j

この形で長さ n の列 a から長さ n の列 b を求めます. ただし n は2の冪乗とします.

やってることは実質 bitDP みたいな感じ.
計算量は O(nlogn) です.

コード
template <typename T>
void fzt(vector<T>& f) {
    int n = f.size();
    for (int i = 1; i < n; i <<= 1) {
        for (int j = 0; j < n; j++) {
            if ((j & i) == 0) {
                f[j] += f[j | i];
                // この場合上位集合の畳み込みになる
                // 左辺と右辺を逆にすると下位集合の畳み込みになる
            }
        }
    }
}

高速メビウス変換(FMT)

ゼータ変換の逆変換

  • Σ(j ⊂ i) (-1)^|i \ j| * a_i = b_j

または

  • Σ(i ⊂ j) (-1)^|j \ i| * a_i = b_j

この形で長さ n の列 a から長さ n の列 b を求めます. ただし n は2の冪乗とします.
計算量は O(nlogn) です.

コード
template <typename T>
void fmt(vector<T>& f) {
    int n = f.size();
    for (int i = 1; i < n; i <<= 1) {
        for (int j = 0; j < n; j++) {
            if ((j & i) == 0) {
                f[j] -= f[j | i];
                // この場合上位集合のゼータ変換の逆変換になる
                // 左辺と右辺を逆にすると下位集合のゼータ変換の逆変換になる
            }
        }
    }
}

高速アダマール変換(FWT)

添え字ANDでの畳み込み

  • Σ(i & j = k) a_i * b_j = c_k

この形で長さ n の列 a, b から長さ n の列 c を求めます. ただし n は2の冪乗とします.

列 a, b を離散アダマール変換をして, かけ合わせてから逆変換すると c が求まります.
上位集合での高速ゼータ変換+高速メビウス変換と同じです.
計算量は O(nlogn) です.

コード
template <typename T>
void fwt(vector<T>& f) {
    int n = f.size();
    for (int i = 1; i < n; i <<= 1) {
        for (int j = 0; j < n; j++) {
            if ((j & i) == 0) {
                f[j] += f[j | i];
            }
        }
    }
}
template <typename T>
void ifwt(vector<T>& f) {
    int n = f.size();
    for (int i = 1; i < n; i <<= 1) {
        for (int j = 0; j < n; j++) {
            if ((j & i) == 0) {
                f[j] -= f[j | i];
            }
        }
    }
}

添え字ORでの畳み込み

  • Σ(i | j = k) a_i * b_j = c_k

この形で長さ n の列 a, b から長さ n の列 c を求めます. ただし n は2の冪乗とします.

ANDとほぼ同じですが, 足される方向が逆になってます.
下位集合での高速ゼータ変換+高速メビウス変換と同じです.
計算量は O(nlogn) です.

コード
template <typename T>
void fwt(vector<T>& f) {
    int n = f.size();
    for (int i = 1; i < n; i <<= 1) {
        for (int j = 0; j < n; j++) {
            if ((j & i) == 0) {
                f[j | i] += f[j];
            }
        }
    }
}
template <typename T>
void ifwt(vector<T>& f) {
    int n = f.size();
    for (int i = 1; i < n; i <<= 1) {
        for (int j = 0; j < n; j++) {
            if ((j & i) == 0) {
                f[j | i] -= f[j];
            }
        }
    }
}

添え字XORでの畳み込み

  • Σ(i ^ j = k) a_i * b_j = c_k

この形で長さ n の列 a, b から長さ n の列 c を求めます. ただし n は2の冪乗とします.

列 a, b を離散アダマール変換をして, かけ合わせてから逆変換すると c が求まります.
ifwt は2で除算する必要があるので注意.
ちなみに, 逆変換は fwt してから全要素を n で割ることでもできます.
計算量は O(nlogn) です.

コード
template <typename T>
void fwt(vector<T>& f) {
    int n = f.size();
    for (int i = 1; i < n; i <<= 1) {
        for (int j = 0; j < n; j++) {
            if ((j & i) == 0) {
                T x = f[j], y = f[j | i];
                f[j] = x + y, f[j | i] = x - y;
            }
        }
    }
}
template <typename T>
void ifwt(vector<T>& f) {
    int n = f.size();
    for (int i = 1; i < n; i <<= 1) {
        for (int j = 0; j < n; j++) {
            if ((j & i) == 0) {
                T x = f[j], y = f[j | i];
                f[j] = (x + y) / 2, f[j | i] = (x - y) / 2;
            }
        }
    }
}

その他

添え字GCDでの畳み込み

kazuma8128.hatenablog.com