表达式的值【题解】

[NOIP2011 普及组] 表达式的值

题目描述

对于1 位二进制变量定义两种运算:

运算的优先级是:

  1. 先计算括号内的,再计算括号外的。

  2. “× ”运算优先于“⊕”运算,即计算表达式时,先计算× 运算,再计算⊕运算。例如:计算表达式A⊕B × C时,先计算 B × C,其结果再与 A 做⊕运算。

现给定一个未完成的表达式,例如_+(_*_),请你在横线处填入数字$0 $或者 1 1 1 ,请问有多少种填法可以使得表达式的值为$0 $。

输入格式

共 2 行。

第1 行为一个整数 L L L,表示给定的表达式中除去横线外的运算符和括号的个数。

第2 行为一个字符串包含 L L L 个字符,其中只包含’(’、’)’、’+’、’*’这 4 4 4 种字符,其中’(’、’)’是左右括号,’+’、’*’分别表示前面定义的运算符“⊕”和“×”。这行字符按顺序给出了给定表达式中除去变量外的运算符和括号。

输出格式

共1 行。包含一个整数,即所有的方案数。注意:这个数可能会很大,请输出方案数对$10007 $取模后的结果。

样例 #1

样例输入 #1

4
+(*)

样例输出 #1

3

提示

【输入输出样例说明】

给定的表达式包括横线字符之后为:_+(_*_)

在横线位置填入(0 、0 、0) 、(0 、1 、0) 、(0 、0 、1) 时,表达式的值均为0 ,所以共有3种填法。

【数据范围】

对于 20 % 20\% 20% 的数据有 0 ≤ L ≤ 10 0 \le L \le 10 0L10

对于 50 % 50\% 50% 的数据有 0 ≤ L ≤ 1 , 000 0 \le L \le 1,000 0L1,000

对于 70 % 70\% 70% 的数据有 0 ≤ L ≤ 10 , 000 0 \le L \le 10,000 0L10,000

对于 100 % 100\% 100%的数据有 0 ≤ L ≤ 100 , 000 0 \le L \le 100,000 0L100,000

对于 50 % 50\% 50% 的数据输入表达式中不含括号。

浅浅分析一波

首先一看数据范围 0 ≤ L ≤ 100000 0 \le L \le 100000 0L100000,回溯的梦想破灭了fuck !

怎么做?
  • 首先,他给的是一个不完整的序列,第一个思路就是把他补齐,例如样例
  • + ( ∗ ) = ? + ( ? ∗ ? ) +(*)=?+(?*?) +()=?+(??)
  • 然后?
  • 如果我们要算,那么首先就是确定运算优先级,怎么确定?
  • 当然是转成后缀表达式,例如样例
  • ? + ( ? ∗ ? ) = ? ? ∗ ? + ?+(?*?)=??*?+ ?+(??)=???+
  • 然后?
  • 由于他问的是方案数,还要 m o d mod mod一个数,很容易想到动态规划
  • 想状态?
  • 很容易想到对于一个操作符,他的方案数由前两个状态得来
  • 什么意思?
  • 例如样例 ? ? ∗ ? + ??*?+ ???+,看到第一个乘号,他的方案数就是前两个 ? ? ?得来的(问号为1)
  • 于是,就变成了 ? ? + ??+ ??+,第一个 ? ? ? ? ? ∗ ??* ??变成的
  • 于是,加法就由前两个 ? ? ?得来的,于是可得下面代码
#include<bits/stdc++.h>

using namespace std;
const int MAXN=1e5+7,mod=1e4+7;
int f[MAXN][2],n,m,len;
char s[MAXN],ss[MAXN];
//f[i][0]为这个位置为0的方案书
//f[i][1]为这个位置为1的方案
//第一步,确定运算顺序
//第二步,dp
//if(s[i-1]=='+') dp[i][0]=dp[i-1][0]
//dp[i][1]=dp[i-1][0]+dp[i-1][1]*2
//if(s[i-1]=='*') dp[i][0]=dp[i-1][0]*2+dp[i-1][1]
//dp[i][1]=dp[i-1][1]
void get_str(int l,int r){
	for(int i=l;i<=r;i++) if(ss[i]=='*') s[++len]=ss[i],ss[i]=' ';
	for(int i=l;i<=r;i++) if(ss[i]=='+') s[++len]=ss[i],ss[i]=' ';
}
void init(){
	stack<int> t;
	ss[0]='(',ss[m+1]=')';
	for(int i=0;i<=m+1;i++){
		if(ss[i]==')'){
			get_str(t.top()+1,i-1);
			t.pop();
		}
		else if(ss[i]=='(') t.push(i);
	}
}
int main(){
	scanf("%d%s",&n,ss+1);
	m=strlen(ss+1);
	init();
	f[1][1]=1,f[1][0]=1;
	for(int i=2;i<=len+1;i++) {
		if(s[i-1]=='*') f[i][0]=(f[i-1][0]*2+f[i-1][1])%mod,f[i][1]=f[i-1][1]%mod;
		if(s[i-1]=='+') f[i][0]=f[i-1][0]%mod,f[i][1]=(f[i-1][0]+f[i-1][1]*2)%mod;
	}
	cout<<f[len+1][0];
	return 0;
}
  • 20 20 20分,为什么?
  • 我们来看一个样例 ( + ) ∗ ( + ) (+)*(+) (+)(+),后缀表达式为 ? ? + ? ? + ∗ ??+??+* ??+??+
  • 我们来看最后一个乘号,根据是上面代码, ∗ * ? , + ?,+ ?,+转移过来,真的是这样吗?
  • 很容易想到,肯定不是这样的,就说明这个 d p dp dp有问题
  • 咋办?
  • 很容易想到用一个,把求出的状态推进去,于是就有了满分代码

C o d e Code Code


#include<bits/stdc++.h>

using namespace std;
struct node{int l,y;};
const int MAXN=1e6+7,mod=1e4+7;
char a[MAXN],S[MAXN],s[MAXN];
int n,m,len;
stack<node> ans;
void print(){
	for(int i=1;i<=len;i++) cout<<s[i];
	cout<<endl;
}
int get_y(char s){
	if(s=='(') return 0;
	if(s=='+') return 1;
	if(s=='*') return 2;
}
void change(){
	stack<char> t;
	for(int i=1;i<=m;i++){
		if(S[i]=='_'){
			s[++len]=S[i];
		}else if(t.empty()){
			t.push(S[i]);
		}else if(S[i]=='('){
			t.push(S[i]);
		}else if(S[i]==')'){
			while(!t.empty()&&t.top()!='(') s[++len]=t.top(),t.pop();
			t.pop();
		}else if(get_y(t.top())>=get_y(S[i])){
			while(!t.empty()&&get_y(t.top())>=get_y(S[i])){s[++len]=t.top();t.pop();}
			t.push(S[i]);
		}else{
			t.push(S[i]);
		}
	} 
	while(!t.empty())s[++len]=t.top(),t.pop();
}
int main(){
	scanf("%d%s",&n,a+1);
	n=strlen(a+1);
	for(int i=1;i<=n;i++){
		if(a[i-1]==')') S[++m]=a[i]; 
		else if(a[i]!='('&&a[i]!=')')S[++m]='_',S[++m]=a[i];
		else if(a[i]=='(') S[++m]=a[i];
		else S[++m]='_',S[++m]=a[i];
	}
	if(a[n]!=')') S[++m]='_';
	change();
//	print();
	for(int i=1;i<=len;i++){
		if(s[i]=='_'){
			ans.push(node{1,1});
		}else{
			node qian=ans.top();ans.pop();
			node hou=ans.top();ans.pop();
			int ysum=0,lsum=0;
			if(s[i]=='+'){
				ysum=(qian.l*hou.y%mod+qian.y*hou.l%mod+qian.y*hou.y%mod)%mod;
				lsum=(qian.l*hou.l)%mod;
			}else{
				lsum=(qian.l*hou.y%mod+qian.y*hou.l%mod+qian.l*hou.l%mod)%mod;
				ysum=(qian.y*hou.y)%mod;
			}
			ans.push(node{lsum,ysum});
		}
	}
	cout<<ans.top().l;
	return 0;
}
/*
0
(+)*(+)
——+——+*
*/
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
算法思路: 1. 创建两个栈:一个操作数栈和一个操作符栈。 2. 从左到右遍历表达式,遇到操作数就压入操作数栈,遇到操作符就与操作符栈中的栈顶元素比较优先级。 3. 如果当前操作符的优先级大于操作符栈顶的操作符优先级,就将当前操作符压入操作符栈。 4. 如果当前操作符的优先级小于或等于操作符栈顶的操作符优先级,就从操作数栈中弹出两个操作数,从操作符栈中弹出一个操作符,进行计算并将结果压入操作数栈,重复步骤3,直到当前操作符的优先级大于操作符栈顶的操作符优先级。 5. 当遍历完表达式后,如果操作符栈中还有操作符,就将操作数栈中剩余的操作数和操作符栈中的操作符按照步骤4计算,直到操作符栈为空。 6. 操作数栈中最后剩下的元素就是表达式算法实现: ```python def calculate(s: str) -> int: # 定义操作符优先级 priority = {'+': 1, '-': 1, '*': 2, '/': 2} # 定义操作数栈和操作符栈 nums = [] ops = [] i = 0 while i < len(s): # 跳过空格 if s[i] == ' ': i += 1 continue # 如果是数字,压入操作数栈 if s[i].isdigit(): j = i while j < len(s) and s[j].isdigit(): j += 1 nums.append(int(s[i:j])) i = j # 如果是操作符,比较优先级 else: while ops and priority[ops[-1]] >= priority[s[i]]: b = nums.pop() a = nums.pop() op = ops.pop() if op == '+': nums.append(a + b) elif op == '-': nums.append(a - b) elif op == '*': nums.append(a * b) elif op == '/': nums.append(int(a / b)) ops.append(s[i]) i += 1 # 处理剩余的操作符 while ops: b = nums.pop() a = nums.pop() op = ops.pop() if op == '+': nums.append(a + b) elif op == '-': nums.append(a - b) elif op == '*': nums.append(a * b) elif op == '/': nums.append(int(a / b)) # 返回操作数栈中最后剩下的元素 return nums[-1] ``` 算法分析: 1. 时间复杂度:遍历一遍表达式,时间复杂度为O(n);操作符栈和操作数栈中每个元素都只进出一次,所以总时间复杂度为O(n)。 2. 空间复杂度:操作符栈和操作数栈的最大长度为表达式的长度,所以空间复杂度为O(n)。 参考链接: 1. [LeetCode官方题解](https://leetcode-cn.com/problems/basic-calculator-ii/solution/jian-dan-zhan-jie-fa-by-jerry_nju/) 2. [算法珠玑(第2版)](https://book.douban.com/subject/33437322/)

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值