All IOI entries
Competitive Programming

IOI 1997 - 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...

Source sync Apr 19, 2026
Track IOI
Year 1997
Files TeX and C++
Folder competitive_programming/ioi/1997/sentiment
IOI1997TeXC++

Source-first archive entry

This page is built from the copied files in competitive_programming/ioi/1997/sentiment. Edit competitive_programming/ioi/1997/sentiment/solution.tex to update the written solution and competitive_programming/ioi/1997/sentiment/solution.cpp to update the implementation.

The website does not replace those files with hand-maintained HTML. It reads the copied source tree during the build and exposes the exact files below.

Problem Statement

Rendered from the "Problem Statement" section inside solution.tex because this entry has no separate statement file.

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

Rendered from the copied solution.tex file. The original TeX source remains available below.

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

Exact copied C++ implementation from solution.cpp.

C++ competitive_programming/ioi/1997/sentiment/solution.cpp

Exact copied implementation source.

Raw file
// 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

Exact copied source-of-truth files. Edit solution.tex for the write-up and solution.cpp for the implementation.

TeX write-up competitive_programming/ioi/1997/sentiment/solution.tex

Exact copied write-up source.

Raw file
\documentclass[11pt,a4paper]{article}
\usepackage[utf8]{inputenc}
\usepackage{amsmath,amssymb}
\usepackage{geometry}
\geometry{margin=2.5cm}
\usepackage{listings}
\usepackage{xcolor}
\usepackage{hyperref}

\lstset{
  language=C++,
  basicstyle=\ttfamily\small,
  keywordstyle=\color{blue}\bfseries,
  commentstyle=\color{green!60!black},
  stringstyle=\color{red!70!black},
  numbers=left,
  numberstyle=\tiny\color{gray},
  numbersep=8pt,
  frame=single,
  breaklines=true,
  tabsize=4,
  showstringspaces=false
}

\title{IOI 1997 -- Sentiment (Expression Evaluation)}
\author{Solution}
\date{}

\begin{document}
\maketitle

\section{Problem Statement}

Given a logical expression containing variables (single lowercase letters), the operators
AND (\texttt{\&}), OR (\texttt|}), and NOT (\texttt{!}), with parentheses for grouping,
evaluate the expression for given variable assignments.

Operator precedence (highest to lowest): NOT, AND, OR.
Parentheses override precedence.

\medskip
\noindent\textbf{Constraints:} Expression length up to 10{,}000 characters;
variables are single lowercase letters.

\section{Solution Approach}

\subsection{Recursive Descent Parser}

We implement a recursive descent parser that directly mirrors the grammar:

\begin{verbatim}
  expr     -> and_expr ('|' and_expr)*
  and_expr -> not_expr ('&' not_expr)*
  not_expr -> '!' not_expr | atom
  atom     -> variable | '(' expr ')'
\end{verbatim}

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:
\texttt{parseExpr} handles OR (lowest precedence), \texttt{parseAnd} handles AND,
and \texttt{parseNot} handles NOT (highest precedence).

\section{C++ Solution}

\begin{lstlisting}
#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;
}
\end{lstlisting}

\section{Correctness}

The parser processes each character exactly once, advancing the global position
\texttt{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.

\section{Complexity Analysis}

\begin{itemize}
  \item \textbf{Time complexity:} $O(n)$ where $n$ is the expression length.
        Each character is visited at most once.
  \item \textbf{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.
\end{itemize}

\end{document}