Sentiment (Expression Evaluation)
Problem Statement Given a logical expression containing variables (single lowercase letters), the operators AND (\&), OR (|), and NOT (!), with parentheses for grouping, evaluate the expression for given variable assi...
Problem Statement
Rendered from the "Problem Statement" section in the LaTeX write-up.
Given a logical expression containing variables (single lowercase letters), the operators AND (&), OR (\texttt|}), and NOT (!), with parentheses for grouping, evaluate the expression for given variable assignments.
Operator precedence (highest to lowest): NOT, AND, OR. Parentheses override precedence.
Constraints: Expression length up to 10{,}000 characters; variables are single lowercase letters.
Editorial
The solution write-up is rendered from the LaTeX source, with equations kept live through MathJax.
Solution Approach
Recursive Descent Parser
We implement a recursive descent parser that directly mirrors the grammar:
expr -> and_expr ('|' and_expr)*
and_expr -> not_expr ('&' not_expr)*
not_expr -> '!' not_expr | atom
atom -> variable | '(' expr ')'
Each grammar rule becomes a function that consumes input characters and returns the boolean value of the corresponding sub-expression. The precedence is naturally encoded: parseExpr handles OR (lowest precedence), parseAnd handles AND, and parseNot handles NOT (highest precedence).
C++ Solution
#include <cstdio>
#include <cstring>
using namespace std;
char expr_str[10005];
int pos, len;
int vars[26]; // variable values for 'a'-'z'
int parseExpr();
int parseAnd();
int parseNot();
int parseAtom();
// expr -> and_expr ('|' and_expr)*
int parseExpr() {
int val = parseAnd();
while (pos < len && expr_str[pos] == '|') {
pos++;
val = val | parseAnd();
}
return val;
}
// and_expr -> not_expr ('&' not_expr)*
int parseAnd() {
int val = parseNot();
while (pos < len && expr_str[pos] == '&') {
pos++;
val = val & parseNot();
}
return val;
}
// not_expr -> '!' not_expr | atom
int parseNot() {
if (pos < len && expr_str[pos] == '!') {
pos++;
return !parseNot();
}
return parseAtom();
}
// atom -> variable | '(' expr ')'
int parseAtom() {
if (expr_str[pos] == '(') {
pos++; // skip '('
int val = parseExpr();
pos++; // skip ')'
return val;
}
int idx = expr_str[pos] - 'a';
pos++;
return vars[idx];
}
void stripSpaces(char* s) {
int j = 0;
for (int i = 0; s[i]; i++)
if (s[i] != ' ')
s[j++] = s[i];
s[j] = '\0';
}
int main() {
fgets(expr_str, sizeof(expr_str), stdin);
// Remove trailing newline
len = strlen(expr_str);
if (len > 0 && expr_str[len-1] == '\n')
expr_str[--len] = '\0';
stripSpaces(expr_str);
len = strlen(expr_str);
// Read variable assignments
int numVars;
scanf("%d", &numVars);
for (int i = 0; i < numVars; i++) {
char c;
int v;
scanf(" %c %d", &c, &v);
vars[c - 'a'] = v;
}
pos = 0;
printf("%d\n", parseExpr());
return 0;
}
Correctness
The parser processes each character exactly once, advancing the global position pos monotonically. The grammar is unambiguous (left-to-right evaluation within each precedence level), so the parser correctly evaluates the expression.
Each grammar production handles exactly the operators at its precedence level and delegates higher-precedence constructs to the next function.
Complexity Analysis
Time complexity: $O(n)$ where $n$ is the expression length. Each character is visited at most once.
Space complexity: $O(n)$ for the recursion stack (worst case: deeply nested parentheses or chained NOT operators). $O(1)$ additional space for the variable table.
Code
C++ solution used for this page.
// IOI 1997 - Sentiment (Expression Evaluation)
// Recursive descent parser for boolean expressions with &, |, !
// Precedence: ! > & > |
// Time: O(n), Space: O(n) recursion stack
#include <bits/stdc++.h>
using namespace std;
char expr_str[10005];
int pos, len;
int vars[26]; // variable values a-z
int parseExpr();
int parseAnd();
int parseNot();
int parseAtom();
// expr -> and_expr ('|' and_expr)*
int parseExpr() {
int val = parseAnd();
while (pos < len && expr_str[pos] == '|') {
pos++;
val = val | parseAnd();
}
return val;
}
// and_expr -> not_expr ('&' not_expr)*
int parseAnd() {
int val = parseNot();
while (pos < len && expr_str[pos] == '&') {
pos++;
val = val & parseNot();
}
return val;
}
// not_expr -> '!' not_expr | atom
int parseNot() {
if (pos < len && expr_str[pos] == '!') {
pos++;
return !parseNot();
}
return parseAtom();
}
// atom -> variable | '(' expr ')'
int parseAtom() {
if (pos < len && expr_str[pos] == '(') {
pos++; // skip '('
int val = parseExpr();
pos++; // skip ')'
return val;
}
// variable: single lowercase letter
int idx = expr_str[pos] - 'a';
pos++;
return vars[idx];
}
void stripSpaces(char* s) {
int j = 0;
for (int i = 0; s[i]; i++)
if (s[i] != ' ')
s[j++] = s[i];
s[j] = '\0';
}
int main() {
// Read expression
fgets(expr_str, sizeof(expr_str), stdin);
int slen = strlen(expr_str);
if (slen > 0 && expr_str[slen - 1] == '\n') expr_str[slen - 1] = '\0';
stripSpaces(expr_str);
len = strlen(expr_str);
// Read variable assignments
int numVars;
scanf("%d", &numVars);
for (int i = 0; i < numVars; i++) {
char c;
int v;
scanf(" %c %d", &c, &v);
vars[c - 'a'] = v;
}
pos = 0;
printf("%d\n", parseExpr());
return 0;
}
Source Files and Assets
Raw files are still available here when you want the original TeX, C++, or statement assets.