【CF582E】Boolean Function(動態規劃+FWT)
阿新 • • 發佈:2020-12-01
- 有四個\(bool\)變數\(A,B,C,D\)以及相對的\(a,b,c,d\)分別表示它們取反後的值。
- 給定一個布林函式\(F(A,B,C,D)\)的表示式\(s\),其中若干變數和運算子缺失。
- 給出\(n\)組\(A,B,C,D\)以及對應的\(F(A,B,C,D)\)值,求有多少種可能的表示式。
- \(|s|\le500,n\le16\)
建立二叉樹
乍一看錶達式求值似乎需要大模擬,但仔細一想這道題還是非常良心的,因為它的表示式定義非常嚴謹,而且每個變數外面也都套著個括號。
於是我們建立一棵二叉樹,每個節點表示原表示式的一個子表示式,然後根據中間的運算子再把它分成兩部分。
最後劃分到只剩一個變數時就可以作為葉節點了。
動態規劃
考慮設\(f_{x,i}\)表示對於二叉樹上的節點\(x\),\(n\)個\(F(A,B,C,D)\)中這個子表示式的值狀壓為\(i\)的方案數。
葉節點直接根據它的變數確定\(f\)的初值。
否則,對於一個點\(x\),設其兩個子節點分別為\(lc,rc\),得到轉移:(其中\(\texttt{opt}\)表示這個子表示式中間的運算子)
\[f_{x,i\ \texttt{opt}\ j}\texttt{+=}f_{lc,i}\times f_{rc,j} \]顯然這就是一個多項式乘法的形式,直接上\(FWT\)優化就好了。
程式碼:\(O(|s|n2^n)\)
#include<bits/stdc++.h> #define Tp template<typename Ty> #define Ts template<typename Ty,typename... Ar> #define Reg register #define RI Reg int #define Con const #define CI Con int& #define I inline #define W while #define S 500 #define N 16 #define X 1000000007 #define Inc(x,y) ((x+=(y))>=X&&(x-=X)) using namespace std; int n,m,l,f[S+5][1<<N],v[5];char s[S+5]; I void FWT_A(int *s,CI op)//FWT與變換 { RI i,j,k;for(i=1;i<=m;i<<=1) for(j=0;j<=m;j+=i<<1) for(k=0;k^i;++k) op?Inc(s[j+k],s[i+j+k]):Inc(s[j+k],X-s[i+j+k]); } I void FWT_O(int *s,CI op)//FWT或變換 { RI i,j,k;for(i=1;i<=m;i<<=1) for(j=0;j<=m;j+=i<<1) for(k=0;k^i;++k) op?Inc(s[i+j+k],s[j+k]):Inc(s[i+j+k],X-s[j+k]); } I void Mul_A(CI x,CI lc,CI rc)//與 { FWT_A(f[x],1),FWT_A(f[lc],1),FWT_A(f[rc],1); for(RI i=0;i<=m;++i) f[x][i]=(1LL*f[lc][i]*f[rc][i]+f[x][i])%X;//轉移 FWT_A(f[x],0),FWT_A(f[lc],0),FWT_A(f[rc],0); } I void Mul_O(CI x,CI lc,CI rc)//或 { FWT_O(f[x],1),FWT_O(f[lc],1),FWT_O(f[rc],1); for(RI i=0;i<=m;++i) f[x][i]=(1LL*f[lc][i]*f[rc][i]+f[x][i])%X;//轉移 FWT_O(f[x],0),FWT_O(f[lc],0),FWT_O(f[rc],0); } int cur,tot;I int DP()//動態規劃(強制進入時cur在左括號,離開時cur在右括號右邊) { RI x=++tot;if(s[cur+2]==')')//只有單個變數(葉節點) { if(s[cur+1]=='?') for(RI i=0;i^4;++i) ++f[tot][v[i]],++f[tot][v[i]^m];//對於未知變數,可以任填 else (s[cur+1]<='D'?f[tot][v[s[cur+1]-'A']]:f[tot][v[s[cur+1]-'a']^m])=1;//對於已知變數,初值唯一 return cur+=3,x; } RI lc,rc;char op;++cur,lc=DP(),op=s[cur],++cur,rc=DP();//op記錄中間運算子,把表示式分成兩部分 return op^'|'&&(Mul_A(x,lc,rc),0),op^'&'&&(Mul_O(x,lc,rc),0),++cur,x;//轉移 } int main() { RI i,j,x;scanf("%s%d",s+1,&n),l=strlen(s+1),s[0]='(',s[l+1]=')';//方便起見,給表示式套上一對括號 for(m=(1<<n)-1,i=0;i^n;++i) for(j=0;j^5;++j) scanf("%d",&x),v[j]|=x<<i;//狀壓 return printf("%d\n",f[DP()][v[4]]),0;//求整個表示式值符合限制的方案數 }