高速ゼータ変換について

これは [データ構造とアルゴリズム Advent Calendar 2018](https://qiita.com/zakuroishikuro/items/f33929eab9d55c5bd073) の 25 日目の記事です。

## 突然ですが
次の問題を解いてみましょう。

>整数 $N$ と $A_0, A_1, A_2, ... A_{2^N-1}$ が与えられます。
$s, t$ に対して $s\ AND\ t=t$ の時、$t \subset s$ と表記することにします。
$0≦i<2^N$ を満たす整数 $i$ について、$j \subset i$ を満たす整数 $j$ についての $A_j$ の総和をそれぞれ求めなさい。
ただし、$a\ AND\ b$ は $a$ と $b$ のbitごとの論理積を求める演算とします。 

>$1≦N≦10$
$1≦A_i≦10^9\ (0≦i<2^N)$
実行時間制限 2秒

問題文にある $s\ AND\ t=t$ が何を言っているのかちょっと分かりにくいかもしれませんが、これは $t$ で立っている (=1になっている) bitは $s$ でも立っているということです。
例を示します。

$N=2, A=[1, 10, 100, 1000]$ のとき
$0≦i<2^N$ を満たす整数 $i$ は $0, 1, 2, 3$
$i=0$ のとき、$i\ AND\ j=j$ を満たす $j$ は $0$ しかないので、答えは $1$
$i=1$ のとき、$i\ AND\ j=j$ を満たす $j$ は $0,1$ なので、答えは $11$
$i=2$ のとき、$i\ AND\ j=j$ を満たす $j$ は $0,2$ なので、答えは $101$
$i=3$ のとき、$i\ AND\ j=j$ を満たす $j$ は $0,1,2,3$ なので、答えは $1111$

この問題は条件を愚直に試していくことで解くことができます。
$i$ の探索に $O(2^N)$ で、$j$ の探索に $O(2^N)$ の計算量が必要なので、全体の計算量はその積の $O(4^N)$ になります。
$N≦10$ なので $4^N≦1048576$ となり、実行時間制限の2秒には十分に間に合います。

```c++
for(int i = 0; i < (1 << N); i++){
    int ans = 0;
    for(int j = 0; j < (1 << N); j++){
        if((i & j) == j){
            ans += A[j];
        }
    }
    cout << ans << endl;
}
```

## ちょっとしたテクニック
※この節は高速ゼータ変換とは関係がないので、この節を飛ばして次の"本題"の節を読んでもらっても大丈夫です。

では、次の問題はどうでしょうか。

>整数 $N$ と $A_0, A_1, A_2, ... A_{2^N-1}$ が与えられます。
$0≦i<2^N$ を満たす整数 $i$ について、$j \subset i$ を満たす整数 $j$ についての $A_j$ の総和をそれぞれ求めなさい。

>$1≦N≦15$
$1≦A_i≦10^9\ (0≦i<2^N)$
実行時間制限 2秒

前の問題と同じように見えますが、よく見ると制約が少し変わっています。
前の問題では $1≦N≦10$ だったのが $1≦N≦15$ になっています。
前と同じように愚直に解こうとすると $N=15$ のとき $4^N=1073741824$ となり、2秒の実行時間制限に間に合いません。

ここで、テクニックを使うことで高速に解くことができます。
先ほど

```c++
for(int j = 0; j < (1 << N); j++){
    if((i & j) == j){
        ans += A[j];
    }
}
```

としていた部分を

```c++
for(int j = i; j >= 0; j--){
    if((i & j) == j){
        ans += A[j];
    }
}
```

とします。`int j = i` の部分が気になるかもしれませんが、$j \subset i$ の時は必ず $j≦i$ なので変数 `j` の初期値を `i` にしても大丈夫です。
まだこれでは計算量は変わっていません。そして、これを

```c++
for(int j = i; j > 0; j = (j - 1) & i){
    ans += A[j];
}
ans += A[0];
```

とします。$j - 1$ は2進法で考えると、$j$ で立っているbitのうち最下位のものを消し、それ未満のbitを全て立てたものになります。
$j$ を自分で実際に決めて $j$ と $j - 1$ を2進法で表してみるとこれが分かると思います。

そして、`(j - 1) & i` は $j - 1$ の立っているbitのうち $i$ でも立っているものだけを取り出したものになります。
これも実際にfor文の動きを辿ってみると分かると思います。

ただし、`j` が 0 の場合、`(0 - 1)` は -1 になりますが、これはC++では内部的に `111..1` であり、`(j - 1) & i` は `i` になってしまうので `j` が 0 の場合はfor文の外で判定をしています。
また、上のコードでは $j \subset i$ を満たす $j$ をもれなくだぶりなく列挙しているので、2個上のコードにあったif文が必要無くなります。

このテクニックは計算量が少し分かりにくいですが、整数 $x$ の立っているbitの個数を $bitcount(x)$ と表記すると、上のコードのfor文のループ回数は $O(2^{bitcount(i)})$ になるので、問題全体の計算量は $O(3^N)$ になります。(二項定理で $(2+1)^N$ を展開すると分かる)
$N≦15$ なので $3^N≦14348907$ となり、実行時間制限に間に合います。

## 本題
では、今度は次の問題を解いてみましょう。

>整数 $N$ と $A_0, A_1, A_2, ... A_{2^N-1}$ が与えられます。
$0≦i<2^N$ を満たす整数 $i$ について、$j \subset i$ を満たす整数 $j$ についての $A_j$ の総和をそれぞれ求めなさい。 

>$1≦N≦20$
$1≦A_i≦10^9\ (0≦i<2^N)$
実行時間制限 2秒

$N≦20$ なので $3^N$ は最大で $3486784401$ になり、$O(3^N)$ 時間のアルゴリズムでも間に合いません。
やっと本題になりますが、ここで **高速ゼータ変換** というアルゴリズムを使うことで $O(2^NN)$ 時間で解くことができます。

具体的には、次のようなDPをします。

- `dp[i][j]` $= k \subset i$ を満たし、かつ $k$ の下から $j$ 番目以上 (0-indexed) のbitの状態が $i$ のそれと同じような整数 $k$ についての $A_k$ の総和

まず、`dp[i][0]` $= A_i\ (0≦i<2^N)$ になります。
そして、

- $i$ の下から $j - 1$ 番目 (0-indexed) のbitが立っているなら `dp[i][j] = dp[i & ~(1 << (j - 1))][j - 1] + dp[i][j - 1]` $(1≦j≦N)$ 
- $i$ の下から $j - 1$ 番目 (0-indexed) のbitが立っていないなら `dp[i][j] = dp[i][j - 1]` $(1≦j≦N)$ 

と更新できます。
また、求めたい答えは `dp[i][N]` になっています。
よって、全体のコードはこうなります。

```c++
int dp[1 << N][N + 1];
for(int i = 0; i < (1 << N); i++){
    dp[i][0] = A[i];
}
for(int j = 0; j < N; j++){
    for(int i = 0; i < (1 << N); i++){
        if(i & (1 << j)){
            dp[i][j + 1] = dp[i & ~(1 << j)][j] + dp[i][j];
        }else{
            dp[i][j + 1] = dp[i][j];
        }
    }
}
for(int i = 0; i < (1 << N); i++){
    cout << dp[i][N] << endl;
}
```

次のように、DPテーブルを再利用することで使うメモリを $O(2^N)$ まで減らすことができます。

```c++
int dp[1 << N];
for(int i = 0; i < (1 << N); i++){
    dp[i] = A[i];
}
for(int j = 0; j < N; j++){
    for(int i = 0; i < (1 << N); i++){
        if(i & (1 << j)){
            dp[i] += dp[i & ~(1 << j)];
        }
    }
}
for(int i = 0; i < (1 << N); i++){
    cout << dp[i] << endl;
}
```

DPテーブルに配列 A を使うことで、初期化をしなくてもよくなります。

```c++
for(int j = 0; j < N; j++){
    for(int i = 0; i < (1 << N); i++){
        if(i & (1 << j)){
            A[i] += A[i & ~(1 << j)];
        }
    }
}
for(int i = 0; i < (1 << N); i++){
    cout << A[i] << endl;
}
```

計算量は $O(2^NN)$ になります。
$N≦20$ の時 $2^NN≦20971520$ なので、実行時間制限の2秒に間に合います。
## 終わりに
今回は総和でしたが、総和でなくても解ける場合があります。(交換法則と結合法則が成り立てば良い)
このアルゴリズムは競プロでは稀にしか出てきませんが、稀に出てくるので覚えておいて損はありません。

最後にこのアルゴリズムを使う問題を挙げておきます。

<details><summary>ネタバレ注意</summary>

[AtCoder Regular Contest 100 - E   Or Plus Max](https://atcoder.jp/contests/arc100/tasks/arc100_c) (DPテーブルの要素がそれぞれ2つの整数を持つ場合です)
[No.767 配られたジャパリまん - yukicoder](https://yukicoder.me/problems/no/767) (高速ゼータ変換の他に包除原理というテクニックを使います)
[日本情報オリンピック 2018年本選 - 5   毒蛇の脱走](https://www.ioi-jp.org/joi/2017/2018-ho/2018-ho-t5.pdf) (こちらも包除原理を使います、とても難しい)
</details>