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>不需要排序,节省时间