模板展开

只维护变量值的长度,直接赋值立即求值,间接赋值保存操作数并在使用时递归展开。

OJ: shumeng

题目 ID: CSP202503C

难度:普及+/提高-

标签:模拟递归字符串

日期: 2026-07-31 16:21

形式化题目

nn 条语句按顺序执行,每条语句是下列三种之一:

  • 1 <var> <expr>:直接赋值,求表达式的值作为变量当前值;
  • 2 <var> <expr>:间接赋值,只记录表达式,使用时再求;
  • 3 <var>:输出变量当前值的长度对 109+710^9+7 取模的结果。

表达式由空格分隔的操作数拼接而成,$x 表示变量 xx 的值,其余为普通字符串。变量初始值为空串,依赖关系保证无环。

思路

真正需要的只有每个变量"当前值的长度",字符串内容完全不参与后续计算,因此全程只维护长度。

直接赋值与间接赋值的区别

  • 直接赋值:立即求表达式,之后长度固定,不受其它变量后续变化影响。
  • 间接赋值:保存操作数列表。每次用到该变量时都要重新展开它依赖的变量,因此它的长度会随依赖变量的更新而动态变化。

朴素递归解释器

先用一个不带缓存的递归解释器验证语义:遇到间接变量就递归展开它的表达式。

cpp
/**
 * Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
 * rbook: -> https://rbook.roj.ac.cn  https://rbook2.roj.ac.cn
 * rainboy的学习导航网站: https://idx.roj.ac.cn
 * create_at: 2026-07-31 16:21
 * update_at: 2026-08-17 22:49
 */
// brute.cpp:小数据暴力解,用来帮助理解题意并辅助对拍。
#include <bits/stdc++.h>
using namespace std;

const long long MOD = 1000000007LL;

struct Variable {
    int type;
    long long length;
    vector<string> expression;
};

map<string, int> ids;
vector<Variable> variables;

int get_id(const string &name) {
    map<string, int>::iterator it = ids.find(name);
    if (it != ids.end()) return it->second;
    int id = (int)variables.size();
    Variable variable;
    variable.type = 0;
    variable.length = 0;
    variables.push_back(variable);
    ids[name] = id;
    return id;
}

// 朴素递归求值:不做任何缓存,每次遇到间接变量都重新展开其依赖变量
long long evaluate(int id) {
    if (variables[id].type == 0) return 0;
    if (variables[id].type == 1) return variables[id].length;

    long long answer = 0;
    for (int i = 0; i < (int)variables[id].expression.size(); i++) {
        string operand = variables[id].expression[i];
        if (!operand.empty() && operand[0] == '$') {
            answer += evaluate(get_id(operand.substr(1)));
        } else {
            answer += (long long)operand.size();
        }
        answer %= MOD;
    }
    return answer;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n;
    cin >> n;
    string line;
    getline(cin, line);
    for (int statement = 0; statement < n; statement++) {
        getline(cin, line);
        stringstream input(line);
        int operation;
        string name;
        input >> operation >> name;
        int id = get_id(name);
        if (operation == 3) {
            cout << evaluate(id) << '\n';
            continue;
        }

        vector<string> expression;
        string operand;
        while (input >> operand) expression.push_back(operand);
        if (operation == 1) {
            long long value = 0;
            for (int i = 0; i < (int)expression.size(); i++) {
                if (!expression[i].empty() && expression[i][0] == '$') {
                    value += evaluate(get_id(expression[i].substr(1)));
                } else {
                    value += (long long)expression[i].size();
                }
                value %= MOD;
            }
            variables[id].type = 1;
            variables[id].length = value;
            variables[id].expression.clear();
        } else {
            variables[id].type = 2;
            variables[id].expression = expression;
        }
    }
    return 0;
}

用时间戳缓存加速

递归展开中同一个变量可能被多次求值。给每次"表达式求值"分配一个递增时间戳 stamp,把本轮已经算出的长度缓存下来;下一轮开始时时间戳改变,缓存自动失效。由于依赖无环,缓存不会造成错误。

代码

cpp
/**
 * Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
 * rbook: -> https://rbook.roj.ac.cn  https://rbook2.roj.ac.cn
 * rainboy的学习导航网站: https://idx.roj.ac.cn
 * create_at: 2026-07-31 16:21
 * update_at: 2026-08-17 22:49
 */
#include <bits/stdc++.h>
using namespace std;

const long long MOD = 1000000007LL;

struct Variable {
    int type;               // 0 未赋值(空串)  1 直接赋值(固定长度)  2 间接赋值(保存表达式)
    long long fixed_length; // type==1 时保存的值长度
    vector<string> expression; // type==2 时保存的操作数列表
};

map<string, int> variable_id;   // 变量名 -> 编号
vector<Variable> variables;     // 所有变量,编号从 0 开始
vector<long long> memo;         // 最近一次求值时该变量的长度缓存
vector<int> seen;               // 标记变量在哪一轮求值中被缓存
int evaluation_stamp = 0;       // 当前求值轮次的时间戳

// 获取变量编号,不存在则新建一个空变量
int get_variable_id(const string &name) {
    map<string, int>::iterator it = variable_id.find(name);
    if (it != variable_id.end()) return it->second;

    int id = (int)variables.size();
    Variable variable;
    variable.type = 0;
    variable.fixed_length = 0;
    variables.push_back(variable);
    variable_id[name] = id;
    memo.push_back(0);
    seen.push_back(0);
    return id;
}

// 求一个变量当前值的长度;同一轮求值内用时间戳缓存避免重复展开
long long evaluate_variable(int id) {
    if (seen[id] == evaluation_stamp) return memo[id];

    long long answer = 0;
    if (variables[id].type == 1) {
        // 直接赋值:长度固定
        answer = variables[id].fixed_length;
    } else if (variables[id].type == 2) {
        // 间接赋值:逐个展开操作数,$ 开头的操作数递归求对应变量
        for (int i = 0; i < (int)variables[id].expression.size(); i++) {
            string operand = variables[id].expression[i];
            if (!operand.empty() && operand[0] == '$') {
                answer += evaluate_variable(get_variable_id(operand.substr(1)));
            } else {
                answer += (long long)operand.size();
            }
            answer %= MOD;
        }
    }

    seen[id] = evaluation_stamp;
    memo[id] = answer;
    return answer;
}

// 求一个表达式的长度:普通字符串贡献字符数,$x 贡献变量 x 当前值的长度
long long evaluate_expression(const vector<string> &expression) {
    evaluation_stamp++;
    long long answer = 0;
    for (int i = 0; i < (int)expression.size(); i++) {
        const string &operand = expression[i];
        if (!operand.empty() && operand[0] == '$') {
            answer += evaluate_variable(get_variable_id(operand.substr(1)));
        } else {
            answer += (long long)operand.size();
        }
        answer %= MOD;
    }
    return answer;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n;
    cin >> n;
    string line;
    getline(cin, line);

    for (int statement = 0; statement < n; statement++) {
        getline(cin, line);
        stringstream input(line);
        int operation;
        string name;
        input >> operation >> name;
        int id = get_variable_id(name);

        // 输出语句:打印该变量当前值的长度模 MOD
        if (operation == 3) {
            evaluation_stamp++;
            cout << evaluate_variable(id) << '\n';
            continue;
        }

        vector<string> expression;
        string operand;
        while (input >> operand) expression.push_back(operand);

        if (operation == 1) {
            // 直接赋值:立刻求值并保存固定长度
            long long value = evaluate_expression(expression);
            variables[id].type = 1;
            variables[id].fixed_length = value;
            variables[id].expression.clear();
        } else {
            // 间接赋值:只保存表达式,使用时再动态求值
            variables[id].type = 2;
            variables[id].expression = expression;
        }
    }

    return 0;
}

复杂度

设一次表达式求值访问了 VV 个变量、处理 TT 个操作数,则本次求值为 O(V+T)O(V+T);每条语句至多触发一次求值,总时间复杂度为 O(n)O(n) 级别,空间复杂度为 O(n)O(n)

总结

间接赋值的本质是"保存表达式而不是保存结果"。把字符串替换成长度后,问题就变成带动态依赖的递归求值;用时间戳缓存可以避免同一轮内的重复展开,且无需显式清空缓存数组。