1. 程式人生 > 實用技巧 >【CF582E】Boolean Function(動態規劃+FWT)

【CF582E】Boolean Function(動態規劃+FWT)

點此看題面

  • 有四個\(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;//求整個表示式值符合限制的方案數
}