2019.06.17課件:[洛谷P1310]表達式的值 題解

P1310 表達式的值

題目描述

給你一個帶括號的布爾表達式,其中+表示或操做|*表示與操做&,先算*再算+。可是待操做的數字(布爾值)不輸入。c++

求能使最終整個式子的值爲0的方案數。spa

題外話

不久以前我在codewars上作過一道相似的題目。c++11

以及把它搬運到了洛谷上。code

布爾表達式計數問題

考慮這樣一個問題:blog

有兩個布爾變量\(x\)\(y\)get

咱們知道使\(x\)等於1的方案有\(x_1\)種,等於0的方案有\(x_0\)種;使\(y\)等於1的方案有\(y_1\)種,等於0的方案有\(y_0\)種。string

那麼:it

使\(x\&y\)爲1的方案數?爲0的方案數?table

使\(x|y\)爲1的方案數?爲0的方案數?class

使\(x\oplus y\)(一般咱們使用\(\oplus\)表示異或)爲1的方案數?爲0的方案數?

不難發現:

使\(x\&y\)爲1,那麼\(x\)\(y\)都要爲1,因此方案數爲\(x_1*y_1\)

使\(x\&y\)爲0,那麼\(x\)\(y\)不能都爲1,因此方案數爲\(x_1*y_0+x_0*y_1+x_0*y_0\)

使\(x|y\)爲1的方案數爲\(x_1*y_1+x_0*y_1+x_1*y_0\),爲0的方案數爲\(x_0*y_0\)

使\(x\oplus y\)爲1的方案數爲\(x_0*y_1+x_1*y_0\),爲0的方案數爲\(x_0*y_0+x_1*y_1\)

表達式樹,前綴表達式,中綴表達式,後綴表達式

表達式樹

\((1+2)*4\)

如上圖,每一個葉節點是一個數字,其餘節點都是(雙目)運算符。

整棵樹表示一個表達式。每一個子樹表示一個子表達式。

計算這個表達式的方式以下圖。

因此值爲12。

中序遍歷

中序遍歷這個表達式樹,咱們發現獲得的結果幾乎和原來的表達式同樣。

只是須要加一些括號罷了。

處理方法:咱們能夠給每一個子樹先後都加一對括號。

前/後序遍歷

稱前序遍歷獲得的式子爲前綴表達式,或者波蘭表達式。稱後序遍歷獲得的式子爲後綴表達式,或者逆波蘭表達式。

前綴表達式和後綴表達式都擁有一個優秀的性質:不須要括號。

(下面僅之後綴表達式爲例)

好比上文的\((1+2)*4\),改成後綴表達式就是:\(1\ 2\ +\ 4\ *\)

如何計算後綴表達式

咱們能夠用棧來處理:

遇到數字,入棧;遇到符號,從棧裏取出兩個數字,按照這個符號運算,而後把結果入棧。最後棧裏剩下的就是結果。

\(1\ 2\ +\ 4\ *\)的計算過程以下:

1入棧 1
2入棧 1 2
1 2出棧,相加得3,3入棧 3
4入棧 3 4
3 4出棧,相乘得12,12入棧 12

因此答案是12。

如何轉化爲後綴表達式

你能夠直接建樹,跑後序遍歷。

可是這樣又很差寫,又慢。

咱們考慮用棧維護。

遍歷中綴表達式:

  1. 遇到數字,直接放入答案序列

  2. 遇到左括號,入棧

  3. 遇到右括號,把棧頂到上一個左括號的元素依次出棧並放入答案序列
  4. 遇到乘號,入棧
  5. 遇到加號,從棧頂開始彈出這段連續的乘號,並放入答案序列,最後加號入棧
  6. 最後把棧裏剩下的元素依次放入答案序列

爲何是正確的?

模擬\(1+1*2*(1+2)+3*2*(1*5)+1\)

說明 答案序列
1放入答案序列 1
+入棧 + 1
1放入答案序列 + 11
*入棧 +* 11
2放入答案序列 +* 112
*入棧 +** 112
(入棧 +**( 112
1放入答案序列 +**( 1121
+入棧 +**(+ 1121
2放入答案序列 +**(+ 11212
出現),+出棧並放入答案序列,(出棧 +** 11212+
出現+,彈出棧頂的*並放入答案序列,而後+入棧 ++ 11212+**
3放入答案序列 ++ 11212+**3
*入棧 ++* 11212+**3
2放入答案序列 ++* 11212+**32
*入棧 ++** 11212+**32
(入棧 ++**( 11212+**32
1放入答案序列 ++**( 11212+**321
*入棧 ++**(* 11212+**321
5放入答案序列 ++**(* 11212+**3215
出現),*出棧並放入答案序列,(出棧 ++** 11212+**3215*
出現+,彈出棧頂的*並放入答案序列,而後+入棧 +++ 11212+**3215***
1放入答案序列 +++ 11212+**3215***1
剩餘棧中元素放入答案序列 11212+**3215***1+++

因此答案是11212+**3215***1+++。

正確性?
\[ \begin{aligned} 11212+**3215***1+++&=112(12+)**32(15*)**1+++\\ &=1123**325**1+++\\ &=11(23*)*3(25*)*1+++\\ &=116*3(10)*1+++\\ &=1(16*)(3(10)*)1+++\\ &=16(30)1+++\\ &=16(31)++\\ &=1(37)+\\ &=38\\ \\ 1+1*2*(1+2)+3*2*(1*5)+1&=1+2*3+6*5+1\\ &=1+6+30+1\\ &=38\\ \end{aligned} \]

P1310題解

首先在輸入的表達式的恰當位置插入未知變量,而後轉爲後綴表達式。固然也能夠一邊轉,一邊插入未知變量。

以後,咱們計算這個後綴表達式的值。不過維護的信息再也不是表達式的值,而是使表達式值爲0或1的方案數。

注意到單個變量爲0或1的方案數爲1.

#include <bits/stdc++.h>
using namespace std;
inline void read(int &num)
{
    bool flag = 0;
    num = 0;
    char c = getchar();
    while ((c < '0' || c > '9') && c != '-')
        c = getchar();
    if (c == '-')
    {
        flag = 1;
        c = getchar();
    }
    num = c - '0';
    c = getchar();
    while (c >= '0' && c <= '9')
        num = (num << 3) + (num << 1) + c - '0', c = getchar();
    if (flag)
        num *= -1;
}
inline void output(int num)
{
    if (num < 0)
    {
        putchar('-');
        num = -num;
    }
    if (num >= 10)
        output(num / 10);
    putchar(num % 10 + '0');
}
inline void outln(int num)
{
    output(num);
    puts("");
}
inline void outln(string str)
{
    puts(str.c_str());
}
//以上爲頭文件和快讀
const int mod = 10007;
const int N = 100001;
int n;
char str[N];            //輸入的中綴表達式
stack<char> sta;        //轉後綴表達式時使用的棧
string final;           //後綴表達式(答案序列)
stack<int> zero, one;   //zero維護使表達式值爲0的方案個數,one維護使表達式值爲1的方案個數
int main()
{
    read(n);
    scanf("%s", str + 1);
    final.push_back('n');                   //後綴表達式最開始應該有一個未知變量
    for (int i = 1; i <= n; i++)
    {
        if (str[i] == '(' || str[i] == '*') //遇到左括號或乘號,入棧
            sta.push(str[i]);
        if (str[i] == '+')                  //遇到加號,彈出棧頂的乘號,而後加號入棧
        {
            while (!sta.empty() && sta.top() == '*')
            {
                final.push_back(sta.top());
                sta.pop();
            }
            sta.push(str[i]);
        }
        if (str[i] == ')')                  //右括號,把到上一個左括號的元素出棧放入答案序列
        {
            while (sta.top() != '(')
            {
                final.push_back(sta.top());
                sta.pop();
            }
            sta.pop();
        }
        if (str[i] != '(' && str[i] != ')') //當不是左括號或者右括號時,應該插入一個未知變量
        {
            final.push_back('n');
        }
    }
    while (!sta.empty())                    //剩下的元素放入答案序列
    {
        final.push_back(sta.top());
        sta.pop();
    }
    for (char c : final)                    //遍歷後綴表達式,這裏使用了c++11的寫法,至關於
//  for (int i = 0; i < final.size(); i++)
//  {   char c = final[i];
    {
        if (c == 'n')                       //單個變量,方案數爲1
        {
            one.push(1);
            zero.push(1);
        }
        else
        {
            //rone表示右操做數(即上文中的y)爲1的方案數(即上文中的y1),rzero同理
            int rone = one.top(), rzero = zero.top();
            one.pop();
            zero.pop();
            //同理
            int lone = one.top(), lzero = zero.top();
            one.pop();
            zero.pop();
            if (c == '*')   //與操做,爲1須要都爲1,爲0須要不都爲1
            {
                one.push(lone * rone % mod);
                zero.push((lone * rzero % mod + lzero * rone % mod + lzero * rzero % mod) % mod);
            }
            else            //或操做,爲0須要都爲0,爲1須要不都爲0
            {
                zero.push(lzero * rzero % mod);
                one.push((lone * rzero % mod + lzero * rone % mod + lone * rone % mod) % mod);
            }
        }
    }
    outln(zero.top());//須要整個表達式的值爲0
}
相關文章
相關標籤/搜索