【Codeforces 582E】Boolean Function

状压DP,FWT。

题目大意:

给定一个布尔函数 $f(A,B,C,D)$,函数值为一个布尔表达式,其中变量为 $A,B,C,D,a,b,c,d$,满足 $a=\neg A,b=\neg B,c=\neg C,d=\neg D$。运算符号只有 &|。保证运算符连接的左右两边都是带一层括号的完整子表达式。

其中有一些运算符(不是括号)和变量消失了,变成了 ?。要求把它们还原。

已知 $n$ 个限制条件形如 $A_i,B_i,C_i,D_i,q_i$,表示 $f(A_i,B_i,C_i,D_i)=q_i$,问满足这些限制条件的填充方案有多少种。

题解:

首先建出表达式树。由于一个运算符连接的两边一定是括号括起来的子表达式,所以表达式树形态唯一。建树可以直接 $O(|s|^2)$ 暴力。

我们令 $F_s$ 表示在一个结点处,其第 $i$ 条限制的函数值为 $s_i$(即当前的子树代表的函数 $f’(A_i,B_i,C_i,D_i)=s_i$)时的方案数。进行状压 DP。

把两个子表达式通过运算符合并的时候,如果运算符是与,则进行集合交卷积。如果是或,则进行集合并卷积。如果是问号,则表示与和或都有可能,所以把两种卷积卷起来的答案加起来即可。

集合交、并卷积可以使用 FWT 完成。

于是总时间复杂度 $O(|s|^2+2^nn|s|)$。

Code:

#include<cstdio>
#include<cstring>
#include<cassert>
typedef long long LL;
#define rep(i,a,b)for(int i=(a);i<=(b);++i)
const int N=1<<16,md=1e9+7;
char s[555];
int n,P[16],F[N],C;
inline void add(int&a,const int&b){a+=b-md,a+=a>>31&md;}
inline void sub(int&a,const int&b){a-=b,a+=a>>31&md;}
#define def(f,g)void f(int*a){for(int i=1;i<C;i<<=1)for(int j=0;j<C;j+=i<<1)for(int k=0;k<i;++k)g;}
def(FWT1,add(a[j+k],a[j+k+i]))
def(IFWT1,sub(a[j+k],a[j+k+i]))
def(FWT2,add(a[j+k+i],a[j+k]))
def(IFWT2,sub(a[j+k+i],a[j+k]))
int A[N],B[N];
#define def_(k)\
void trans##k(int*a,int*b,int*F){\
    for(int i=0;i<C;++i)A[i]=a[i],B[i]=b[i];\
    FWT##k(A),FWT##k(B);\
    for(int i=0;i<C;++i)A[i]=(LL)A[i]*B[i]%md;\
    IFWT##k(A);\
    for(int i=0;i<C;++i)add(F[i],A[i]);\
}
def_(1)def_(2)
void solve(int l,int r,int*F){
    int fh=0,mid=-1;
    for(int i=l;i<=r&&mid==-1;++i)
    switch(s[i]){
        case'(':++fh;break;
        case')':--fh;break;
        default:if(!fh)mid=i;
    }
    if(mid==-1||l==r){
        while(s[l]=='('&&s[r]==')')++l,--r;
        assert(l==r);
        for(int x=1;x<=4;++x)if(s[l]=='?'||s[l]=='A'+4-x||s[l]=='a'+4-x){
            int zt=0;
            for(int i=0;i<n;++i)zt|=(P[i]>>x&1)<<i;
            if(s[l]!='a'+4-x)++F[zt];
            if(s[l]!='A'+4-x)++F[(C-1)^zt];
        }
        return;
    }
    int L[C],R[C];
    for(int i=0;i<C;++i)L[i]=R[i]=0;
    solve(l+1,mid-2,L),solve(mid+2,r-1,R);
    if(s[mid]!='|')trans1(L,R,F);
    if(s[mid]!='&')trans2(L,R,F);
}
int main(){
    scanf("%s%d",s,&n);
    for(int i=0;i<n;++i){
        int a,b,c,d,e;
        scanf("%d%d%d%d%d",&a,&b,&c,&d,&e);
        P[i]=16*a+8*b+4*c+2*d+e;
    }
    C=1<<n;
    solve(0,strlen(s)-1,F);
    int zt=0;
    for(int i=0;i<n;++i)zt|=(P[i]&1)<<i;
    printf("%d\n",F[zt]);
    return 0;
}