[洛谷 P1310]表达式的值 --- 表达式树(中缀转后缀)+DP

传送门:洛谷 P1310


题目描述

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

这里写图片描述
运算的优先级是:

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

× × × ”运算优先于“ ⨁ \bigoplus ”运算,即计算表达式时,先计算 × × × 运算,再计算 ⨁ \bigoplus 运算。例如:计算表达式 A ⨁ B × C A\bigoplus B×C AB×C时,先计算 B × C B × C B×C,其结果再与 A A A ⨁ \bigoplus 运算。

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


分析

无论是用栈做还是用树做,其目的都是理清 D P DP DP顺序。
注意题目省略了数字(类比吧),自行脑补一下

一.中缀表达式转后缀表达式

(放在前面强调)对于本题来说,数字要自己补( 0 / 1 0/1 0/1的方案数均为 1 1 1)

对于一棵表达式树而言,叶子节点全是数字,非叶子节点全是操作符

基于手动算法:

  1. 补全括号: a + b ∗ c − ( d + e ) → ( ( a + ( b ∗ c ) ) − ( d + e ) ) a+b*c-(d+e) \to ((a+(b*c))-(d+e)) a+bc(d+e)((a+(bc))(d+e))

  2. 将运算符放在括号后面: ( ( a + ( b ∗ c ) ) − ( d + e ) ) → ( ( a ( b c ) ∗ ) + ( d e ) + ) − ((a+(b*c))-(d+e)) \to ((a(bc)* )+ (de)+ )- ((a+(bc))(d+e))((a(bc))+(de)+)

  3. 去掉所有括号: ( ( a ( b c ) ∗ ) + ( d e ) + ) − → a b c ∗ + d e + − ((a(bc)* )+ (de)+ )- \to abc*+de+- ((a(bc))+(de)+)abc+de+

稍加分析改进后(不必加括号),就有了如下算法:

  1. 判断两边是否有多余的括号,有的话去掉

  2. 从后往前(正常计算时,左子树会先计算,所以,对于同级的操作符来说,前面的归为左子树),找到优先级最低且最后面的运算符,以它为父亲节点。

  3. 左右子树递归处理

二.DP

令f[i][0/1]表示子树i的为0/1的方案数

友情提示:注意取模(否则 20 20 20

if(节点i为 ‘+) 
	f[i][0] = f[lch][0] * f[rch][0];
	f[i][1] = f[lch][0] * f[rch][1] + f[lch][1] * f[rch][0] + f[lch][1] * f[rch][1];
        
if(节点i为 '*' )
	f[i][0] = f[lch][0] * f[rch][0] + f[lch][0] * f[rch][1] + f[lch][1] * f[rch][0];
	f[i][1] = f[lch][1] * f[rch][1];

代码

#include <cstdio>
#include <cstdlib>
#include <stack>

#define IL inline

using namespace std;

IL int read()
{
    int sum = 0, k= 1;
    char c = getchar();
    
    for(;'0' > c || c > '9'; c = getchar())
    if(c == '-') k = 0;
    
    for(;'0' <= c && c <= '9'; c = getchar())
        sum = sum * 10 + c - '0';
    return k ? sum : -sum;
}

const int mo(10007);
int n;

char mp[100005];
int to[100005];
stack<int>stk;

IL int turn(char c)
{
    if(c == '+') return 1;
    if(c == '*') return 2;
    return 3;
}

IL int find(int l, int r)
{
    int p = -1;
    for(; r >= l;)
    if(mp[r] == ')')
    {
        r = to[r] - 1;
    }else
    {
        if(p == -1 || turn(mp[r]) < turn(mp[p])) p = r;
        if(mp[p] == '+') return p;
        --r;
    }
    return p;
}

struct node
{
    int x, y;
    IL node(int x_ = 0, int y_ = 0)
    {
        x = x_; y = y_;
    }
};

IL node dfs(int l, int r)
{
    if(l > r) return node(1, 1);

    if(mp[l] == '(' && mp[r] == ')' && to[r] == l) { ++l; --r; }
    if(l > r) return node(1, 1);
    int p = find(l, r);
    
    //printf("%d %c\n", p, mp[p]);
    
    node tmp1 = dfs(l, p - 1), tmp2 = dfs(p + 1, r);
    if(mp[p] == '+') return node( tmp1.x * tmp2.x % mo, (tmp1.x * tmp2.y % mo + tmp1.y * tmp2.x % mo + tmp1.y * tmp2.y % mo) % mo);
    if(mp[p] == '*') return node( (tmp1.x * tmp2.x % mo + tmp1.x * tmp2.y % mo + tmp1.y * tmp2.x % mo) % mo, tmp1.y * tmp2.y % mo);
}

int main()
{
    n = read();
    char c;
    
    for(int i = 1; i <= n; ++i)
    {	
        scanf(" %c", &c);
        mp[i] = c;
        if(c == '(') stk.push(i); else
        if(c == ')') { to[i] = stk.top(); stk.pop(); }
    }

    printf("%d\n", dfs(1, n).x);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值