
1 a hello
1 b world
2 c $a $b
1 d good $c
1 a hi
1 e good $c
1 a hello
1 b world
2 c $a $b
3 c
1 a hi
3 c
将会输出:10 和 7,对应的变量的值为:
helloworld
hiworld
需要注意的是,在使用间接赋值语句时,在变量的值之间建立了依赖关系,在上述模板中,变量 c 的值依赖于变量 a 和 b 的值。 可以想见,如果变量的值间的依赖关系形成了环,那么模板将无法执行。我们约定,给定的表达式中不存在这样的环。
形式化地,模板语言用 BNF 表示如下:
CHAR ::= [a-z0-9]
SPACE ::= ' '
DOLLAR ::= '$'
NONEMPTY_STRING ::= CHAR | NONEMPTY_STRING CHAR
STRING ::= '' | NONEMPTY_STRING
VAR ::= NONEMPTY_STRING
OPERAND ::= DOLLAR VAR | NONEMPTY_STRING
EXPR ::= OPERAND | EXPR SPACE OPERAND
ASSIGN_OP ::= '1' | '2'
ASSIGN ::= ASSIGN_OP SPACE VAR SPACE EXPR
OUTPUT ::= '3' SPACE VAR
STATEMENT ::= ASSIGN | OUTPUT
输入格式
从标准输入读入数据。
输入的第一行为一个整数 nn,表示模板语言的语句数量。接下来的 nn 行,每行为一个语句,语句的格式如上所述。
输出格式
输出到标准输出。
依次执行输入的语句:如果语句为输出语句,则相应输出一行变量的值的长度除以 1000000007 的余数。
样例1输入
6
1 a hello
1 b world
2 c $a $b
3 c
1 a hi
3 c
样例1输出
10
7
样例1解释
本样例即为题目描述中的例子。
样例2输入
5
1 var value
3 var
3 val
1 var $var $val $var
3 var
样例2输出
5
0
10
样例2解释
执行第一条语句后,变量 var 的值为 value,因此第二条语句输出 5;执行第三条语句时,变量 val 并未被赋值,因此其值为初始的空字符串,输出 0;执行第四条语句后,变量 var 的值为 valuevalue,因此第五条语句输出 10。
子任务
| 测试点 | nn | 语句类型 | 表达式的性质 |
|---|---|---|---|
| 1, 2 | ≤200≤200 | 无间接赋值语句 | 每个表达式仅有1个字符串操作数 |
| 3, 4 | 每个表达式仅有1个操作数 | ||
| 5, 6 | 每个表达式包含不超过100个操作数 | ||
| 7, 8 | 包含所有类型语句 | ||
| 9, 10 | ≤2000≤2000 |
全部的数据满足:变量的总数不超过 1000 个,且每个变量的名称、字符串的长度不超过 50 个字符。
解答
题目描述很长,是一道字符串题,难度方面,我觉得要是把stringstream和map结合起来用就是一道中等难度或者送分题,废话不多说,直接开整:
题目大意:
输入n行字符串作为表达式,且表达式第一个字符代表着操作类型
第一个字符为1:直接赋值表达式
格式:1 + 变量名 + 一些操作数(空格分开)
如样例2:1 var $var $val $var 就是var赋值三个操作数,分别是$var,$val,$var
其中$符号代表该操作数是一个变量,需要根据变量名(不含$)找到它的存的字符串
如果该变量不存在,则缺省为空字符串
第一个字符为2:动态赋值表达式
如样例1中:2 c $a $b
这里c是赋值为$a $b,可知是变量a和变量b储存的字符串相加,但是不能直接把a和b储存的字符串赋值给c,因为c是动态赋值,c需要随着a和b的改变而改变,所以我们想到把c赋值为$a $b,等到需要用c的时候再用一个函数读取变量a和变量b。
第一个字符串为3:输出表达式
5
1 var value
3 var
3 val
1 var $var $val $var
3 var
输出表达式格式:3 + 变量名
根据变量名输出该变量储存的字符串长度
如样例2:
先把value赋值给var
输出变量var字符串长度为5
然后输出变量val长度,因为val不存在,故缺省为空字符串,输出0
把var赋值为$var $val $var,也就是valuevalue,输出10
思路大致就是这样,解决这个问题用 stringstream 很合适,很舒服,因为这题的输入有大量包含空格的字符串,而stringstream可以按照空格分隔读取字符串
1. stringstream 是什么?
stringstream 是 C++ 中 <sstream> 头文件提供的类,用于在字符串中进行类似 cin/cout 的格式化输入输出操作。stringstream 常用于一次性解析一行中的多个字段。
2. 使用 stringstream 读取字符串的方法
方法一:使用 >> 按空格分割读取,这是最常用的方式,适用于将字符串按空格分词读入多个变量:
#include <iostream>
#include <sstream>
#include <string>
using namespace std;int main() {string input = "hello world 123";stringstream ss(input);string word1, word2;int num;ss >> word1 >> word2 >> num;cout << "Word1: " << word1 << endl; // hellocout << "Word2: " << word2 << endl; // worldcout << "Num: " << num << endl; // 123
}
方法二:使用 getline() 读取整行或指定分隔符的内容
如果你想以某个特定符号作为分隔符,比如逗号 , 或冒号 :,可以这样写:
#include <iostream>
#include <sstream>
#include <string>
using namespace std;int main() {string input = "apple,banana,orange";stringstream ss(input);string token;while (getline(ss, token, ',')) {cout << token << endl;}
}
总结:stringstream 读取字符串方法对比
| 方法 | 用途 | 示例 |
|---|---|---|
|
| 按空格/换行/Tab 分隔读取 |
|
|
| 按指定分隔符读取整段字符串 |
|
特点1:stringstream 可以控制读取变量的个数
它不会自动读取所有内容,而是根据你写多少次 >> 来决定读几个变量。所以你可以通过控制使用 >> 的次数来精确地控制从 stringstream 中读取多少个变量。
#include <iostream>
#include <sstream>
#include <string>int main() {std::string line = "apple banana cherry date";std::stringstream ss(line);std::string a, b, c;// 仅读取前两个变量if (ss >> a >> b) {std::cout << "a: " << a << "\n"; // 输出: applestd::cout << "b: " << b << "\n"; // 输出: banana}// 再读一个(第三个)if (ss >> c) {std::cout << "c: " << c << "\n"; // 输出: cherry}// 剩下的部分可以继续读,也可以忽略
}
在这个例子中:
- 我们只读了三个变量 (
a,b,c),即使字符串中有四个单词; - 第四个单词
"date"没有被读取。
控制方式总结
| 方法 | 说明 |
|---|---|
|
| 明确读取两个变量 |
| 使用 | 避免越界或格式错误 |
| 结合 | 可以循环读取全部变量 |
如果你想读取不确定数量的变量
比如一行中有多个空格分隔的单词,你可以用循环:
#include <iostream>
#include <sstream>
#include <string>
#include <vector>int main() {std::string line = "one two three four five";std::stringstream ss(line);std::vector<std::string> words;std::string word;while (ss >> word) {words.push_back(word);}std::cout << "Total words: " << words.size() << "\n";for (const auto& w : words)std::cout << w << "\n";
}
>>会跳过空白字符(空格、换行、Tab);- 如果你希望保留空格,应该使用
getline(); - 如果输入格式不一致,建议加判断防止程序崩溃;
- 多余的数据不会报错,只是留在流里未被读取;
特点二:接着读
stringstream 在读取时会维护一个内部的位置指针(读指针) ,如果你没有一次性把内容全部读完,那么下一次读取的时候,它会从上次结束的位置继续读取 。
🧠 类比理解
你可以把 stringstream 想象成一个“文件流”或者“输入流”,就像你读一个文件一样,有一个“当前读到哪了”的概念。
#include <iostream>
#include <sstream>
#include <string>int main() {std::string line = "apple banana cherry date";std::stringstream ss(line);std::string a, b;// 第一次读两个单词ss >> a >> b;std::cout << "First read: " << a << " " << b << "\n"; // apple bananastd::string c, d;// 再次读两个单词ss >> c >> d;std::cout << "Second read: " << c << " " << d << "\n"; // cherry date
}
输出
First read: apple banana
Second read: cherry date
补充知识点:如何重置位置指针?
如果你想让 stringstream 重新从开头读一遍 ,可以使用 .seekg() 方法:
std::stringstream ss("hello world");std::string s1, s2;
ss >> s1 >> s2; // s1=hello, s2=worldss.seekg(0); // 回到开头std::string s3, s4;
ss >> s3 >> s4; // s3=hello, s4=world
stringstream的读写行为和cin类似,不是像字符串那样可以反复访问;
总结
| 问题 | 回答 |
|---|---|
|
| ✅ 是的,它维护了一个读指针 |
| 没读完再次读取会不会从头开始? | ❌ 不会,会接着上次的位置继续读 |
| 如何让它从头读? | 使用 |
| 如何清空整个 stringstream? | 可以用 ss.str(""); ss.clear(); |
进阶(可跳过,不用这个用substr截取字符串也行)
如果你想获取 还未被读取的部分字符串 ,可以用下面这种方法:
#include <iostream>
#include <sstream>
#include <string>int main() {std::stringstream ss("hello world this is a test");std::string a, b;ss >> a >> b; // 已经读了 "hello" 和 "world"// 获取剩余部分std::string rest;std::getline(ss, rest); // 从当前指针位置读到结尾std::cout << "Remaining string: " << rest << std::endl;// 输出: " this is a test"
}
std::getline(ss, rest); 会从当前位置开始读到结尾,并且自动跳过前面的空白字符 (如空格、Tab)。如果你不希望它跳过空白,可以这样写:
std::string rest(std::istreambuf_iterator<char>(ss),std::istreambuf_iterator<char>());
map<string, string> mp 的作用详解
✅ 它是一个存储变量名和对应值的容器
map<string, string> mp;
map<key,value>mp
mp用于保存用户定义的所有变量名及其对应的字符串值。- 键(key)是变量名(
string类型) - 值(value)是变量的内容(
string类型)
本题变量名是字符串,储存的值也是字符串,用map建立变量名到储存的实际字符串的映射效果很好,可以用 mp[a] 直接访问变量a储存的字符串
总结:map<string, string> 的好处一览
| 优势 | 描述 |
|---|---|
| ✅ 自动排序(这里没用到) | 键值有序,方便遍历和调试 |
| ✅ 支持默认访问 |
|
| ✅ 无哈希冲突 | 更加理论安全 |
| ✅ 小数据高效 | 对少量变量完全胜任 |
代码一览
#include <bits/stdc++.h>
using namespace std;
map<string, string> mp;
string f(string s)
{if (s[0] == '$')s.erase(0, 1);elsereturn s;if (mp.find(s) != mp.end())return mp[s];elsereturn "!NOT FOUND!";
}
int main()
{int n;cin >> n;cin.ignore();for (int i = 0; i < n; i++){string s;getline(cin, s);// 直接赋值变量if (s[0] == '1'){s.erase(0, 2);stringstream ss(s);string a, b, word;ss >> a;while (ss >> word){b += f(word);// cout << b << endl;}mp[a] = b;// cout << "1:" << a << " " << b << endl;}if (s[0] == '2'){s.erase(0, 2);stringstream ss(s);string a, b;ss >> a;getline(ss, b);b.erase(0, 1);mp[a] = b;// cout << "2:" << a << b << endl;}if (s[0] == '3'){s.erase(0, 2);if (mp.count(s)){stringstream ss(mp[s]);string ans, word;while (ss >> word){ans += f(word);}cout << ans.size() % 1000000007 << endl;// cout << ans << endl;}else{mp[s] = "";cout << 0 << endl;}}}
}
优化:
使用unordered_map<string,string>不需要排序,节省时间