-
給定 \(n, q\) 和 \(f(0), f(1), \dots, f(2^n - 1)\)。\(q\) 次詢問,給定 \(S\),求:
\[\sum_{S' \subseteq S} f(S') \] -
\(n \le 20\),\(q \le 10^6\)。
令 \(S_i\) 表示 \(S\) 的第 \(i\) 個二進位制位。
可以發現若 \(S' \subseteq S\),那麼對於所有 \(i\) 都有 \(S'_i \le S_i\)。也就是要求:
\[\sum_{S'_0 = 0}^{S_0}\sum_{S'_1 = 0}^{S_1} \sum_{S'_2 = 0}^{S_2}\dots, \sum_{S'_{n-1} = 0}^{S_{n-1}} f(S')
\]
發現這就是一個 \(n\) 維的字首和的形式。
對於二維字首和,除樸素的容斥做法 \(s_{i, j} = s_{i, j - 1} + s_{i - 1, j} - s_{i - 1, j - 1} + a_{i, j}\) 外,可以用這種方式:
for (int i = 1; i <= n; ++ i )
for (int j = 1; j <= n; ++ j )
a[i][j] += a[i][j - 1];
for (int i = 1; i <= n; ++ i )
for (int j = 1; j <= n; ++ j )
a[i][j] += a[i - 1][j];
第一個迴圈後,\(a_{i, j} = a_{i, 1} + a_{i, 2} + \dots + a_{i, j}\),表示先對每行單獨做一遍字首和。
第二個迴圈後,\(a_{i, j}\) 就是真正的左上矩陣的和,此時是對每列單獨做一遍字首和。
同理可以延伸到三維:
for (int i = 1; i <= n; ++ i )
for (int j = 1; j <= n; ++ j )
for (int k = 1; k <= n; ++ k )
a[i][j][k] += a[i - 1][j][k];
for (int i = 1; i <= n; ++ i )
for (int j = 1; j <= n; ++ j )
for (int k = 1; k <= n; ++ k )
a[i][j][k] += a[i][j - 1][k];
for (int i = 1; i <= n; ++ i )
for (int j = 1; j <= n; ++ j )
for (int k = 1; k <= n; ++ k )
a[i][j][k] += a[i][j][k - 1];
列舉範圍為 \([0, 1]\) 時就可以用位運算計算了。
我們令答案式為 \(g(S)\)。類比上邊兩份程式碼,可以得到:
for (int j = 0; j < n; ++ j )
for (int i = 0; i < (1 << n); ++ i )
if (i >> j & 1)
g[i] += g[i ^ (1 << j)];