把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【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;//求整个表达式值符合限制的方案数
}
posted @ 2020-12-01 14:32  TheLostWeak  阅读(112)  评论(0编辑  收藏  举报