P1310 [NOIP 2011 普及组] 表达式的值
闲聊:本题正解细节偏多一些,说人话就是不太好调。
首先,原题的 + 和 * 其实就是对应着 | 和 & 运算,感觉这么出题有点诈骗……
之前听一位大佬说,计数题基本上不是排列组合就是 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 就可以。
因此,此时的转移方程就是:
\(u\) 节点的运算是 & 时同理。此时的转移方程如下:
最终答案就是 \(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;
}

浙公网安备 33010602011771号