P1310 [NOIP 2011 普及组] 表达式的值

闲聊:本题正解细节偏多一些,说人话就是不太好调。


题目传送门

My Blog-欢迎光临!

首先,原题的 + 和 * 其实就是对应着 | 和 & 运算,感觉这么出题有点诈骗……

之前听一位大佬说,计数题基本上不是排列组合就是 dp。这个东西不像是能直接排列组合的样子,所以我们考虑一下 dp。

我们看见中缀表达式,考虑建中缀表达式树。如果我们把树建出来了,那这大概会是个树上 dp。

\(dp_{u,0/1}\) 表示考虑完了 \(u\) 及其子树的所有取值后,算到 \(u\) 这个点时答案是 \(0/1\) 的方案数。

第一种情况是,\(u\) 是叶子结点,也就是它这个位置只有一个数字,它为 0 时子树答案就为 0 ,为 1 时子树答案就为 1 ,这样 \(dp_{u,0}=dp{u,1}=1\)

然后我们考虑它有子结点的情况。(此时它一定是一个运算符)

表达式树最多只有两个子节点,我们分别记左儿子和右儿子为 \(ls,rs\)

\(u\) 节点上的运算符是 | 时,让 \(u\) 为 0 ,只能是 \(ls,rs\) 均为 0。而让 \(u\) 为 1,只要 \(ls,rs\) 有一个是 1 就可以。

因此,此时的转移方程就是:

\[\begin{cases} dp_{u,0}=dp_{ls,0} \times dp_{rs,0} \\ dp_{u,1}=dp_{ls,0} \times dp_{rs,1} + dp_{ls,1} \times dp_{rs,0} + dp_{ls,1} \times dp_{rs,1} \\ \end{cases} \]

\(u\) 节点的运算是 & 时同理。此时的转移方程如下:

\[\begin{cases} dp_{u,1}=dp_{ls,1} \times dp_{rs,1} \\ dp_{u,0}=dp_{ls,0} \times dp_{rs,1} + dp_{ls,1} \times dp_{rs,0} + dp_{ls,0} \times dp_{rs,0} \\ \end{cases} \]

最终答案就是 \(dp_{root,0}\)。我们分析了一顿后,现在的难点就只剩建中缀表达式树了。

因为原表达式里没有数字的位置,所以我们考虑找一下数字在哪里。

手玩一些小的测试点后,我们发现数字肯定是出现在运算符号前后的。为了防止多个运算符间重复计算,我们只考虑数字出现在运算符前的情况。

如果一个运算符前没有右括号,那它前面就是左括号或其他运算符。那这里肯定是有数字的。反之如果前面是右括号,那根据常识不能填数字。

但是这样会少算一些数字:比如一层括号里的运算结束后,最后一个运算符右边得有数字。但是最后一个运算符不好找,所以我们找右括号。

我当时的想法是,对于这种情况,在右括号左边加上数字就好了。但是这样你就会获得 80 分的好成绩。。。

原因也很简单:如果是两个右括号靠在一起的情况,很显然中间不能填数字,但是你的代码并不会管这么多,只会一味地帮你填数字。所以你就挂了。

修改也很简单:加一个前一个字符是否是右括号的判断即可。

最后还有一个位置的数字需要考虑:如果不是以右括号结尾的话,那末尾还得有一个数字。

这样我们已经知道了所有数字出现的位置。接下来正常跑中缀表达式即可。(我采用的是用一个数字栈和一个符号栈弹来弹去的方式,具体可以看其他大佬的文章)

这里我没有改原串并加入数字,而是边扫原串边找的数字位置,然后把它扔进数字栈的。感觉会简单一些。

代码:

P1310
#include<bits/stdc++.h>
#define int long long
using namespace std;

inline int read(){
	int x=0,f=1;char c=getchar();
	while(c<48){
		if(c=='-') f=-1;
		c=getchar();
	}
	while(c>47) x=(x<<1)+(x<<3)+(c^48),c=getchar();
	return x*f;
}

inline void write(int x){
	if(x<0) putchar('-'),x=-x;
	if(x<10) putchar(x+'0');
	else write(x/10),putchar(x%10+'0');
}

const int N=2e5+5;
const int mod=10007;
int len,awa,n,h[2*N],tot,dot[2*N],fa[2*N],root,dp[2][2*N],son[2][2*N];
char s[2*N];
stack<int> st1,st2;
struct Nahida{
	int u,v,nxt;
}e[2*N];

inline void add(int u,int v){
	e[++tot]={u,v,h[u]};h[u]=tot;
}

inline void dfs(int u){
	if(!dot[u]){//u是叶子结点 
		dp[0][u]=dp[1][u]=1;
		return ;
	}
	int ls=son[0][u],rs=son[1][u];
	dfs(ls);
	dfs(rs);
	if(dot[u]==1){//u的运算符是|
		dp[0][u]=(dp[0][ls]*dp[0][rs])%mod;
		dp[1][u]=(dp[0][ls]*dp[1][rs]%mod+dp[1][ls]*dp[0][rs]%mod+dp[1][ls]*dp[1][rs]%mod)%mod;
	}
	else{//u的运算符是&
		dp[1][u]=(dp[1][ls]*dp[1][rs])%mod;
		dp[0][u]=(dp[0][ls]*dp[1][rs]%mod+dp[1][ls]*dp[0][rs]%mod+dp[0][ls]*dp[0][rs]%mod)%mod;
		
	}
}

signed main(){
	//& > |
	len=read();scanf("%s",s+1);
	int yxj=0;
	//yxj:优先级 
	//&优先级为2,|优先级为1 
	for(int i=1;i<=len;i++){
		if(s[i]=='('){//一共两种运算,优先级加2就能区分 
			yxj+=2;
		}
		else if(s[i]==')'){
			if(s[i-1]!=')'){//这里有数字 
				st1.push(++awa);
			}
			yxj-=2;
		}
		else if(s[i]=='+'){//|
			if(s[i-1]!=')'){//这里有数字 
				st1.push(++awa);
			}
			while(!st2.empty()&&st2.top()>=1+yxj){//如果栈里面有要优先计算的就计算 
				//取两个数 
				int num1=st1.top();st1.pop();
				int num2=st1.top();st1.pop();
				
				//建新节点 
				++awa;dot[awa]=(st2.top()+1)%2+1;
				st2.pop();
				
				//建树 
				fa[num2]=awa,fa[num1]=awa;
				add(awa,num1);add(awa,num2);
				son[0][awa]=num1,son[1][awa]=num2;
				st1.push(awa);
			}
			st2.push(1+yxj);//入符号栈 
		}
		else if(s[i]=='*'){//&
			if(s[i-1]!=')'){//这里有数字 
				st1.push(++awa);
			}
			while(!st2.empty()&&st2.top()>=2+yxj){//如果栈里面有要优先计算的就计算 
				//取两个数 
				int num1=st1.top();st1.pop();
				int num2=st1.top();st1.pop();
				
				//建新节点 
				++awa;dot[awa]=(st2.top()+1)%2+1;
				st2.pop();
				
				//建树 
				fa[num2]=awa,fa[num1]=awa;
				add(awa,num1);add(awa,num2);
				son[0][awa]=num1,son[1][awa]=num2;
				st1.push(awa);
			}
			st2.push(2+yxj);//入符号栈 
		}
	}
	if(s[len]!=')'){//处理末尾数字 
		st1.push(++awa);
	}
	while(!st2.empty()){//处理没计算的符号 
		//取数字和符号 
		int opt=(st2.top()+1)%2+1;st2.pop();
		int num1=st1.top();st1.pop();
		int num2=st1.top();st1.pop();
				
		//建新节点 
		++awa;dot[awa]=opt;
		add(awa,num1);add(awa,num2);
				
		//建树 
		fa[num1]=awa;fa[num2]=awa;
		son[0][awa]=num1,son[1][awa]=num2;
		st1.push(awa);
	}
	//找根节点 
	for(int i=1;i<=awa;i++){
		if(!fa[i]){
			root=i;break;
		}
	}
	dfs(root);
	int ans=dp[0][root];
	printf("%lld",ans);
	return 0;
}
posted @ 2025-10-28 08:56  qwqSW  阅读(5)  评论(0)    收藏  举报