原題連結:https://www.luogu.com.cn/problem/P1310
題意解讀:+代表按位或運算,*代表按位與運算,給定一個沒有填數字的表示式,要求結果為0的數字方案數。
解題思路:
下面一步一步,由淺入深的來解決本題
思路一(20分做法):
觀察得知,20%的資料,只有10個符號,且沒有括號,也就是對應數字最多11個,可以考慮DFS列舉所有長度是n+1的01數字組合,然後根據運算子、數字進行表示式計算。
需要注意的點是,對於中序表示式,在求值的時候,需要使用兩個棧:運算子棧、運算元棧,主要流程如下:
1、遍歷運算子和運算元
2、如果運算子棧為空,則將運算子和對應兩個運算元入棧
3、如果運算子棧不為空,且當前運算子優先順序不高於棧頂運算子的優先順序,則將棧頂運算子和對應的兩個運算元彈出進行運算,結果入運算元棧
4、再把當前運算子和下一個運算元分別入棧
5、最後,對棧中剩餘的運算子和運算元進行一遍運算
6、結果即運算元棧頂元素
20分程式碼:
#include <bits/stdc++.h>
using namespace std;
const int MOD = 10007;
int n;
string str;
int a[15]; //n+1個0/1數字的組合
int ans = 0;
map<char, int> priority = {{'+', 1} , {'*', 2}};
int eval()
{
stack<char> op; //運算子棧
stack<int> num; //數字棧
for(int i = 0; i < str.size(); i++)
{
if(op.empty()) //運算子棧為空,則將運算子,左右運算元入棧
{
op.push(str[i]);
num.push(a[i]);
num.push(a[i + 1]);
continue;
}
//如果運算子棧不空,且當前運算子優先順序不高於棧頂運算子優先順序
//則彈出棧頂運算子和相應數字,進行計算,之後再把當前運算子和右運算元入棧
if(op.size() && priority[str[i]] <= priority[op.top()])
{
char c = op.top(); op.pop();
int a = num.top(); num.pop();
int b = num.top(); num.pop();
int res;
if(c == '*') res = a & b;
else res = a | b;
num.push(res);
}
op.push(str[i]);
num.push(a[i + 1]);
}
//最後對棧中剩餘運算子和運算元進行計算
while(op.size())
{
char c = op.top(); op.pop();
int a = num.top(); num.pop();
int b = num.top(); num.pop();
int res;
if(c == '*') res = a & b;
else res = a | b;
num.push(res);
}
//最終結果為棧頂數字
return num.top();
}
void dfs(int k)
{
if(k == n + 1) //列舉到一組n + 1個0、1的數的組合
{
if(eval() == 0) ans++; //計算表示式的值
return;
}
a[k] = 0;
dfs(k + 1);
a[k] = 1;
dfs(k + 1);
}
int main()
{
cin >> n >> str;
dfs(0);
cout << ans % MOD;
return 0;
}
思路2(50分做法):
由於前50%的資料長度不超過1000,且沒有括號,可以利用組合數的思想:
對於一個+*組成的表示式,要為0,必須+對應的所有運算元都為0
表示式中可能有多個連續的*,形如+*+**+,完整表示式為_+_*_+_*_*_+_
結果為0,則有_ , _*_, _*_*_, _這四個都是0
_,代表一個數,為0的方案數只有1種
_*_,代表兩個數做與運算,為0的方案有01,10,00 3種,也就是2個數,其中有可能有1個0,2個0,對應的方案數為C(2,1)+C(2,2) = 3種
_*_*_,代表三個數做與運算,為0的方案有C(3,1)+C(3,2)+C(3,3) = 7種
_,代表一個數,為0的方案只有1種
所以,_+_*_+_*_*_+_表示式為0的方案數為1 * 3 * 7 * 1 = 21種
基於以上分析,只需要找到表示式中的連續的*,每cnt個*,代表有cnt+1個數做與運算,為0的方案是C(cnt+1,1)+C(cnt+1,2)+...+C(cnt+1,cnt+1)
所有連續*的方案數相乘即可。
注意需要對組合數進行預處理。
50分程式碼:
#include <bits/stdc++.h>
using namespace std;
const int MOD = 10007;
int n;
string str;
int ans = 1;
int c[1005][1005];
int main()
{
cin >> n >> str;
//初始化組合數
for(int i = 0; i <= 1000; i++)
{
for(int j = 0; j <= i; j++)
{
if(j == 0) c[i][j] = 1;
else c[i][j] = (c[i-1][j-1] + c[i-1][j]) % MOD;
}
}
int cnt = 0;
for(int i = 0; i < str.size(); i++)
{
if(str[i] == '*') cnt++;
else
{
//處理連續cnt個*
if(cnt > 0)
{
int sum = 0; //cnt個*對應的表示式計算為0的方案數,也就是cnt+1個數有幾個1~cnt+1個0的方案數:c(cnt,0)+c(cnt,1)+...+c(cnt,cnt)種
for(int j = 1; j <= cnt + 1; j++)
{
sum += c[cnt+1][j]; sum %= MOD;
}
ans *= sum; ans %= MOD;
cnt = 0;
}
}
}
if(cnt > 0)
{
int sum = 0; //cnt個*對應的表示式計算為0的方案數,也就是cnt+1個數有幾個1~cnt+1個0的方案數:c(cnt,0)+c(cnt,1)+...+c(cnt,cnt)種
for(int j = 1; j <= cnt + 1; j++)
{
sum += c[cnt+1][j]; sum %= MOD;
}
ans *= sum; ans %= MOD;
cnt = 0;
}
cout << ans;
return 0;
}
思路3(100分做法):
如果知道了中綴表示式的計算方法,也知道了此題跟組合數的關係,更進一步可以得出:
如果要計算結果是0的方案數
a+b是0的方案數 = a是0的方案數 * b是0的方案數
a*b是0的方案數 = a是0的方案數 * b是0的方案數 + a是0的方案數 * b是1的方案數 + a是1的方案數 * b是0的方案數
因此,我們可以藉助表示式求值的程式碼流程,但實際進行計算是,不是具體計算兩個數的+或者*,而是遞推的計算
{結果是0的方案數、結果是1的方案數},
運算元棧存的就是一個結構體
struct node
{
int cnt0; //結果為0的方案數
int cnt1; //結果為1的方案數
};
100分程式碼:
#include <bits/stdc++.h>
using namespace std;
const int MOD = 10007;
int n;
string str;
struct node
{
int cnt0; //結果為0的方案數
int cnt1; //結果為1的方案數
};
stack<char> op;
stack<node> num;
map<char, int> priority = {{'+', 1} , {'*', 2}}; //運算子的優先順序
void eval()
{
char c = op.top(); op.pop();
node a = num.top(); num.pop();
node b = num.top(); num.pop();
node res;
if(c == '+')
{
//兩邊都是0,+才是0
res.cnt0 = (a.cnt0 * b.cnt0) % MOD;
//有一邊是1,+就是1
res.cnt1 = (a.cnt0 * b.cnt1 + a.cnt1 * b.cnt0 + a.cnt1 * b.cnt1) % MOD;
}
else if(c == '*')
{
//有一邊是0,*就是0
res.cnt0 = (a.cnt0 * b.cnt1 + a.cnt1 * b.cnt0 + a.cnt0 * b.cnt0) % MOD;
//兩邊都是1,*才是1
res.cnt1 = (a.cnt1 * b.cnt1) % MOD;
}
num.push(res);
}
int main()
{
cin >> n >> str;
num.push({1, 1}); //第一個運算元,只有1位,結果為0和為1的方案數都是1
for(int i = 0; i < str.size(); i++)
{
if(str[i] == '(') op.push(str[i]);
else if(str[i] == ')')
{
while(op.top() != '(') eval();
op.pop(); //彈出')'
}
else
{
while(op.size() && op.top() != '(' && priority[str[i]] <= priority[op.top()])
eval();
op.push(str[i]); //存入當前運算子
num.push({1, 1}); //存入右運算元
}
}
while(op.size()) eval(); //處理棧中剩餘運算子
cout << num.top().cnt0; //資料棧的結果0的方案數即答案
return 0;
}