CF582E - Boolean Function
首先将表达式转换成表达式树。
令 $ f[u][s] $ 表示 $ u $ 这棵子树中,在 $ n $ 组取值的情况下, $ n $ 组结果为 $ s $ 的方案数。
首先每一位的转移是互不影响的,因为只是把他们放到一起而已,所以 $ s1, s2 $ 与 符号 $ opr $ 所转移到的地方就是 $ s1 \ opr \ s2 $ 。
所以可以先写出一个暴力算法。
#include <cstdio>
#include <cctype>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
#define ll long long
#define reg register
#define rep(i, a, b) for (reg int i = (a), i##end = (b); i <= i##end; ++i)
#define dep(i, a, b) for (reg int i = (a), i##end = (b); i >= i##end; --i)
template <typename _typer> inline _typer read() {
_typer init = 0;
char ch = getchar(), k = 0;
for ( ; !isdigit(ch); ch = getchar()) k = (ch == '-');
for ( ; isdigit(ch); ch = getchar())
init = (init << 3) + (init << 1) + (ch ^ 48);
return k ? -init : init;
}
const ll N = 505, INF = 1e9;
const ll M = (1 << 16), Mod = 1e9 + 7;
/*******************************************************************************
*
*
*
******************************************************************************/
int n, tot;
int lson[N], rson[N], node[N], f[N][M], ChTpInt[N], cnt[N];
string s;
struct NODE { int id, rt; } ;
NODE Change(int l, int r) {
int u = ++tot, id = l;
// cerr << l << " " << r << endl;
rep (i, l, r) {
id = i;
if (s[i] == '(') {
NODE tmp = Change(i + 1, r);
if (lson[u]) rson[u] = tmp.rt;
else lson[u] = tmp.rt;
id = i = tmp.id;
continue;
}
// cerr << s[i] << endl;
if (s[i] == ')') break;
if (s[i] != '?') {
node[u] = s[i];
} else if (s[i + 1] == '(') { // opr - ?
node[u] = 1;
} else { // num - ?
node[u] = 2;
}
}
// cerr << u << " " << node[u] << " " << id << endl;
// system("pause");
return (NODE) { id, u };
}
int val[N][10], ans[N];
int Get(int x, int y, int ch) {
if (ch == '|') return x | y;
else if (ch == '&') return x & y;
return 0;
}
void Upd(int &x, int y) { ((x += y) >= Mod && (x -= Mod)); }
void Dfs(int u) {
// cerr << u << " " << ((node[u] == 1 || node[u] == 2) ? node[u] : (char) node[u]) << endl;
if (!u) return ;
if (node[u] == 2) {
rep (i, 1, 8) {
int w = 0;
rep (j, 1, n) if (val[j][i]) w |= (1 << j >> 1);
cnt[u] += (!f[u][w]), ++f[u][w];
}
} else if (isalpha(node[u])) {
int w = 0;
rep (j, 1, n) if (val[j][ChTpInt[node[u]]]) w |= (1 << j >> 1);
cnt[u] += (!f[u][w]), ++f[u][w];
} else {
int ls = lson[u], rs = rson[u];
Dfs(ls), Dfs(rs);
if (cnt[ls] > cnt[rs]) swap(ls, rs);
rep (s1, 0, (1 << n) - 1) if (f[ls][s1]) {
rep (s2, 0, (1 << n) - 1) if (f[rs][s2]) {
int tmp = 1ll * f[ls][s1] * f[rs][s2] % Mod;
if (node[u] == 1) {
Upd(f[u][Get(s1, s2, '|')], tmp);
Upd(f[u][Get(s1, s2, '&')], tmp);
} else Upd(f[u][Get(s1, s2, node[u])], tmp);
}
}
}
}
int main() {
cin >> s;
n = read<int>();
rep (i, 0, 3) ChTpInt['A' + i] = i + 1, ChTpInt['a' + i] = i + 5;
int w = 0;
rep (i, 1, n) {
rep (d, 1, 4) val[i][d + 4] = (val[i][d] = read<int>()) ^ 1;
if (read<int>()) w |= (1 << i >> 1);
}
NODE tmp = Change(0, s.length() - 1);
// cerr << "Change end" << endl;
Dfs(tmp.rt);
printf("%d\n", f[tmp.rt][w]);
return 0;
}
但是这会血 T ,所以要对整个方案进行改进。
先只考虑 &
。
我们要统计的是满足 $ s = s1 & s2 $ 的 $ f[ls][s1] * f[rs][s2] $ 的和,因为 $ s = s1 & s2 $ ,所以 $ s $ 肯定是 $ s1 , s2 $ 最大公共子集。
令 $ sum[s] $ 表示所有包含 $ s $ 的状态的和, $ tmp[s] $ 表示 &
之后状态为 $ s $ 的总和。
那么 $ tmp[s] = sum[ls][s] * sum[rs][s] - \sum tmp[s'] $ ,其中 $ s \subset s' $ 。
为什么要减去包含 $ s $ 的值?因为包含 $ s $ 的两个数可能还有其他公共部分,那么他们 &
之后的结果就是 $ s' $ 了。
关于 $ sum $ 数组我们可以用 高维前(后)缀和 简单的解决掉,但是该如何容斥出 $ tmp $ 数组呢?
我直接类似高维前缀和弄了个高维前缀减,尽管并不知道为什么能这么用, 但是 $ AC $ 了就是好算法(停停停) 。
考虑两个状态 $ s, s' $ ,其中 $ s \subset s' $ ,设从 $ s $ 到 $ s' $ 需要 $ t $ 步。
因为我们把高维前缀和中的 +
变成了 -
,所以因为负负得正, $ s $ 对 $ s' $ 做的贡献就是 $ (-1) ^ t $ 。
$ sum[ls][s] * sum[rs][s] $ 的到的结果是包含所有 $ tmp[s'] (s \in s') $ 的,所以一开始的值都包含了所有的 "孩子" (在上面的博客中我将包含与不包含定义成了 父子关系)。
对于所有 $ s'' $ 满足 二进制位中 只有 那 $ t $ 位上与 $ s $ 有 $ i $ 位不同(即那 $ i $ 位值为 $ 1 $ ,其他几位和 $ s $ 一模一样) ,会对 $ s' $ 贡献 $ s $ , 而每个的贡献是 $ (-1) ^ {t - i} $ ,对于每个 $ i $ 一共有 $ C_{t} ^{i} $ 个这样的数,所以 $ s $ 在 $ s' $ 的系数就是 $ \sum _{i = 0} ^{t} (C_{t} ^{i} * (-1) ^{t - i}) $ (在更新之前系数是 $ 1 $ )。
那个时候还根本不会算这个东西,今天打题解的时候突然想到了前两天刚看的多项式定理,当然这里只用到了 二项式定理。
二项式定理:
\[(x + c) ^t = \sum _{i = 0} ^{t} (C _{t} ^{i} * x^i * c^{t - i}) \]
那么当 $ x = 1, c = -1 $ 时,右边就是上面的式子,而值 $ (1 - 1) ^ t = 0 $ ,即最后在 $ tmp[s'] $ 中不包含 $ tmp[s] $ 。
即证。
(其实还有一种方法,把减法考虑成加法的逆操作,即按照加法的顺序倒着来一遍,就是一个个减掉。而我们的初始状态与加法的终止状态每个值是一致的,所以减法的终止状态就是加法的初始状态。和我说这种方法的那个人没有打博客,所以我在这里提一下)
还要考虑 |
运算。其实或运算就是 补集做了与运算后再取补集,即 $ C _U (s1 | s2) = C _U s1 & C _U s2 $ 。
所以转换成 &
就行了。
void Dfs(int u) {
if (!u) return ;
memset(f, 0, sizeof f);
if (node[u] == 2) {
rep (i, 1, 8) {
int w = 0;
rep (j, 1, n) if (val[j][i]) w |= (1 << j >> 1);
++f[w];
}
} else if (isalpha(node[u])) {
int w = 0;
rep (j, 1, n) if (val[j][ChTpInt[node[u]]]) w |= (1 << j >> 1);
++f[w];
} else {
int ls = lson[u], rs = rson[u];
Dfs(ls), Dfs(rs);
dep (s, uplim, 0) {
tmpS[s] = 1ll * S[ls][s] * S[rs][s] % Mod;
tmpC[s] = 1ll * C[ls][s] * C[rs][s] % Mod;
}
rep (i, 0, n - 1) rep (s, 0, uplim)
if (!(s & (1 << i))) {
Upd(tmpS[s], Mod - tmpS[s | (1 << i)]);
Upd(tmpC[s], Mod - tmpC[s | (1 << i)]);
}
rep (s, 0, uplim) {
if (node[u] == 1) {
// |
f[s] = tmpC[uplim ^ s];
// &
Upd(f[s], tmpS[s]);
} else if (node[u] == '|') {
f[s] = tmpC[uplim ^ s];
} else if (node[u] == '&') {
f[s] = tmpS[s];
}
}
}
rep (s, 0, uplim) S[u][s] = C[u][uplim ^ s] = f[s];
rep (i, 0, n - 1) rep (s, 0, uplim)
if (!(s & (1 << i))) {
Upd(S[u][s], S[u][s | (1 << i)]);
Upd(C[u][s], C[u][s | (1 << i)]);
}
}
$ in \ 2019.9.27 $