洛谷 P7324 - [WC2021] 表达式求值(状压+dp)

题面传送门

现场人傻系列……

首先建出 \(E\) 的表达式树,具体来说表达式的每一个叶子节点表示一个数组 \(A_i\),每一个非叶子节点都表示一次运算,它的值表示左右儿子进行该运算后得到的结果。这个可以通过建表达式树的套路方法在 \(\mathcal O(|E|)\) 的时间内求出来。

其次注意到数组每一位的贡献是独立的,也就是说我们可以对 \(n\) 位每一位都单独求一遍贡献。于是现在等价于我们解决 \(n\) 个如下的问题:

  • 给出一个数组 \(a_1,a_2,\cdots,a_m\) 和一个带问号的表达式 \(E\),其中 \(E\) 有些位置是 \(<\),有些位置是 \(>\),有些位置是 \(?\),你可以将 \(?\) 替换为 \(<\)\(>\),求所有可能得到的表达式的值之和。

这个问题显然可以用树形 \(dp\) 求解,设 \(dp_{i,j}\) 表示执行完表达式树上 \(i\) 子树中所有运算后,得到的值为 \(a_j\) 的填法,转移就分该节点上的运算为 \(<\)\(>\) 转移即可,相当于求一遍 \(\max/\min\) 卷积,时间复杂度 \(n|E|m^2\),当然如果想用前缀和优化可以做到 \(n|E|m\),反正都是暴力,过不去。

考虑优化,注意到 \(n\),也就是子问题个数很多,但是数组长度 \(m\) 非常非常小,并且每次运算的表达式都是一样的。我们考虑借鉴 CF878D 的套路,对每种可能的相对数组大小计算一遍贡献,不过直接枚举相对大小有 \(m!\) 种,再乘上表达式长度 \(5\times 10^4\),还是会 TLE。考虑对上面的子问题进行转化,对于一个子问题我们将这个子问题对应的 \(a\) 数组从小到大排个序,设为 \(b_1,b_2,\cdots,b_m\),显然最终任意一个表达式运算的结果一定出自这 \(m\) 个数,对于一个运算结果为 \(b_i\) 的表达式,我们不直接加上 \(b_i\) 的贡献,instead 我们用差分的思想,显然 \(\forall j\le i,b_i\ge b_j\),也就是说我们可以对于所有 \(i\) 统计运算得到的结果 \(\ge b_i\) 的表达式个数 \(c_i\),并令答案加上 \((b_i-b_{i-1})\times c_i\),不难发现这样算下来每种运算结果为 \(b_i\) 的表达式会产生 \((b_i-b_{i-1})+(b_{i-1}-b_{i-2})+\cdots+(b_1-b_0)=b_i\) 的贡献,符合题意。

我们考虑怎样统计运算得到的结果 \(\ge b_i\) 的表达式个数,我们回到原来的 \(a\) 数组,记 \(c_j=[a_j\ge b_i]\),对于一种表达式,我们将 \(c\) 数组带回到表达式进行运算,如果运算结果为 \(1\) 那么真正的运算结果 \(\ge b_i\),反之真正的运算结果 \(<b_i\),正确性显然。这样转化有什么好处呢?不难发现这样一转化,所有数都缩小到了 \([0,1]\) 范围内,可能的情况个数也就降到了 \(2^m\),于是我们对这 \(2^m\) 分别预处理一遍即可,即 \(f_{S}\) 为当 \(\forall x\in S,c_x=1,\forall x\notin S,c_x=0\) 时有多少个表达式运算得到的值为 \(1\),对于每个 \(S\) 显然可以在 \(\mathcal O(|E|)\) 内求出 \(f_S\),那么如果我们记 \(ST_i\)\(\{j|a_j\ge b_i\}\),那么运算得到的结果 \(\ge b_i\) 的表达式个数为 \(f_{ST_i}\)。把 \(a\) 排个序后随便搞搞就好了,时间复杂度 \(|E|2^m+mn\log m\)

const int MAXM=10;
const int MAXN=5e4;
const int MAXP=1<<10;
const int MOD=1e9+7;
int n,m,len,a[MAXM+5][MAXN+5],mch[MAXN+5];char s[MAXN+5];
int ch[MAXN*2+5][2],op[MAXN*2+5],ncnt=0,rt=0;
ll dp[MAXN*2+5][2],f[MAXP+5];pii p[MAXM+5];
int build(int l,int r){
	int id=++ncnt;
	if(l==r) return op[id]=s[l]-'0',id;
	if(s[r]==')'){
		if(mch[r]==l) id=build(l+1,r-1);
		else{
			op[id]=(s[mch[r]-1]=='<')?10:((s[mch[r]-1]=='>')?11:12);
			ch[id][0]=build(l,mch[r]-2);
			ch[id][1]=build(mch[r],r);
		}
	} else {
		op[id]=(s[r-1]=='<')?10:((s[r-1]=='>')?11:12);
		ch[id][0]=build(l,r-2);
		ch[id][1]=build(r,r);
	}
	return id;
}
void dfs(int x,int st){
	if(!ch[x][0]&&!ch[x][1]){dp[x][~st>>op[x]&1]=1;return;}
	dfs(ch[x][0],st);dfs(ch[x][1],st);
	if(op[x]!=10){
		dp[x][0]=(dp[x][0]+1ll*dp[ch[x][0]][0]*dp[ch[x][1]][0])%MOD;
		dp[x][1]=(dp[x][1]+1ll*dp[ch[x][0]][1]*dp[ch[x][1]][0]+1ll*dp[ch[x][0]][0]*dp[ch[x][1]][1]+1ll*dp[ch[x][0]][1]*dp[ch[x][1]][1])%MOD;
	} if(op[x]!=11){
		dp[x][1]=(dp[x][1]+1ll*dp[ch[x][0]][1]*dp[ch[x][1]][1])%MOD;
		dp[x][0]=(dp[x][0]+1ll*dp[ch[x][0]][1]*dp[ch[x][1]][0]+1ll*dp[ch[x][0]][0]*dp[ch[x][1]][1]+1ll*dp[ch[x][0]][0]*dp[ch[x][1]][0])%MOD;
	}
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=0;i<m;i++) for(int j=1;j<=n;j++) scanf("%d",&a[i][j]);
	scanf("%s",s+1);len=strlen(s+1);stack<int> st;
	for(int i=1;i<=len;i++){
		if(s[i]=='(') st.push(i);
		if(s[i]==')') mch[i]=st.top(),st.pop();
	} rt=build(1,len);
	for(int i=0;i<(1<<m);i++){
		memset(dp,0,sizeof(dp));dfs(rt,i);
		f[i]=dp[rt][1];//printf("%d %d\n",i,f[i]);
	} int ans=0;
	for(int i=1;i<=n;i++){
		for(int j=0;j<m;j++) p[j]=mp(a[j][i],j);
		sort(p,p+m);int msk=0;
		for(int j=0;j<m;j++){
			ans=(ans+1ll*f[msk]*(p[j].fi-p[j-1].fi))%MOD;
			msk|=(1<<p[j].se);
		}
	} printf("%d\n",ans);
	return 0;
}
posted @ 2021-03-27 11:16  tzc_wk  阅读(98)  评论(0)    收藏  举报