自用
中缀表达式计算
AcWing 3302. 表达式求值 - AcWing
#include<iostream>
using namespace std;
#include<stack>
#include<unordered_map>
unordered_map<char,int> pr={{'+',1},{'-',1},{'*',2},{'/',2}};
stack<int> num;
stack<char> op;
void eval(){
int b=num.top();num.pop();
int a=num.top();num.pop();
char c=op.top();op.pop();
int x;
if(c=='+')x=a+b;
else if(c=='-')x=a-b;
else if(c=='*')x=a*b;
else x=a/b;
num.push(x);
}
int main(){
string str;
cin>>str;
for(int i=0;i<str.size();i++){
char c=str[i];
// cout<<c<<endl;
if(isdigit(c)){
int j=i;
int x=0;
while(isdigit(str[j])){
x=x*10+str[j]-'0';
j++;
}
num.push(x);
i=j-1;
}
else if(c=='('){
op.push(c);
}
else if(c==')'){
while(op.top()!='(')eval();
op.pop();
}
else {
while(op.size()&&op.top()!='('&&pr[op.top()]>=pr[c])eval();
op.push(c);
}
}
while(op.size())eval();
cout<<num.top();
}
AcWing 3273. 二十四点 - AcWing
就是中缀表达式套壳
#include<iostream>
using namespace std;
#include<stack>
#include<unordered_map>
unordered_map<char,int> pr={{'+',1},{'-',1},{'x',2},{'/',2}};
stack<int> num;
stack<char> op;
void eval(){
int b=num.top();num.pop();
int a=num.top();num.pop();
char c=op.top();op.pop();
int x;
if(c=='+')x=a+b;
else if(c=='-')x=a-b;
else if(c=='x')x=a*b;
else x=a/b;
//cout<<"push"<<x<<endl;
num.push(x);
}
int main(){
int n;
cin>>n;
while(n--){
while(num.size())num.pop();
while(op.size())op.pop();
string str;
cin>>str;
for(int i=0;i<str.size();i++){
char c=str[i];
if(isdigit(c)){
int j=i;
int x=0;
while(isdigit(str[j])){
x=x*10+str[j]-'0';
j++;
}
num.push(x);
i=j-1;
}
else if(c=='('){
op.push(c);
}
else if(c==')'){
while(op.top()!='(')eval();
op.pop();
}
else {
while(op.size()&&op.top()!='('&&pr[op.top()]>=pr[c])eval();
op.push(c);
}
}
while(op.size())eval();
//cout<<num.top()<<" ";
if(num.top()==24)cout<<"Yes\n";
else cout<<"No\n";
}
}
表达式的转换
题目描述
平常我们书写的表达式称为中缀表达式,因为它将运算符放在两个操作数中间,许多情况下为了确定运算顺序,括号是不可少的,而后缀表达式就不必用括号了。
后缀标记法:书写表达式时采用运算紧跟在两个操作数之后,从而实现了无括号处理和优先级处理,使计算机的处理规则简化为:从左到右顺序完成计算,并用结果取而代之。
例如:8-(3+2*6)/5+4
可以写为:8 3 2 6 * + 5 / - 4 +
其计算步骤为:
8 3 2 6 * + 5 / - 4 +
8 3 12 + 5 / - 4 +
8 15 5 / - 4 +
8 3 - 4 +
5 4 +
9
编写一个程序,完成这个转换,要求输出的每一个数据间都留一个空格。
输入格式
就一行,是一个中缀表达式。输入的符号中只有这些基本符号 0123456789+-*/^()
,并且不会出现形如 2*-3
的格式。
表达式中的基本数字也都是一位的,不会出现形如 12
形式的数字。
所输入的字符串不要判错。
输出格式
若干个后缀表达式,第 i+1i+1 行比第 ii 行少一个运算符和一个操作数,最后一行只有一个数字,表示运算结果。
输入输出样例
输入 #1复制
8-(3+2*6)/5+4
输出 #1复制
8 3 2 6 * + 5 / - 4 + 8 3 12 + 5 / - 4 + 8 15 5 / - 4 + 8 3 - 4 + 5 4 + 9
输入 #2复制
2^2^3
输出 #2复制
2 2 3 ^ ^ 2 8 ^ 256
说明/提示
运算的结果可能为负数,/
以整除运算。并且中间每一步都不会超过 231231。字符串长度不超过 100100。
注意乘方运算 ^
是从右向左结合的,即 2 ^ 2 ^ 3
为 2 ^ (2 ^ 3)
,后缀表达式为 2 2 3 ^ ^
。
其他同优先级的运算是从左向右结合的,即 4 / 2 / 2 * 2
为 ((4 / 2) / 2) * 2
,后缀表达式为 4 2 / 2 / 2 *
。
保证不会出现计算乘方时幂次为负数的情况,故保证一切中间结果为整数。
这题难度并不是很大,就是模拟,只是代码很长而已。只要思路明确,就可通过此题。
一、题意
-
写出给定字符串的后缀表达式。
-
写出计算过程。
二、数据范围
-
字符只有
0123456789+-\*/^
几种。 -
基本数字只有一位数,不会出现多位数。
-
输入数字不会出现负数,中间一切结果为整数。
-
字符串长度小于 100100。
三、思路
3.1 第一问
3.1.1 认识后缀表达式的转换方法
首先,我们要知道如何手算得出后缀表达式。下面是题目样例的后缀表达式转换方法。
8 - (3 + 2 * 6) / 5 + 4
8 - (3 + 2 6 *) / 5 + 4
8 - 3 2 6 * + / 5 + 4 (去括号)
8 - 3 2 6 * + 5 / + 4
8 3 2 6 * + 5 / - + 4
8 3 2 6 * + 5 / - 4 +
那么如何用编程来实现呢?
3.1.2 程序基本实现
用字符串 ss 存储中缀表达式。
我们定义一个函数来判断运算符的优先级。如下:
int check(char c)
{
switch(c)
{
case '+':return 1;
case '-':return 1;
case '*':return 2;
case '/':return 2;
case '^':return 3;
case '(':return 0;
case ')':return 0;
default:return -1;//程序不会执行这句,保险起见要加上
}
}
定义两个栈 datdat 和 opop,它们存的分别是后缀表达式和符号。
来看例子:2 + 3 * 4
模拟一下:
栈dat | 栈op | 操作 |
---|---|---|
2 | Null | 将 2 入栈 |
2 | + | op 栈为空,将 + 入栈 |
3 2 | + | 将 3 入栈 |
3 2 | * + | * 比 + 优先级高,入栈 |
4 3 2 | * + | 将 4 入栈 |
+ * 4 3 2 | Null | 弹空 op 栈,依次进入 dat 栈 |
这时,逆序输出栈 datdat,得到式子的后缀表达式 2 3 4 * +
。
再看一个:2 * 4 + 3
栈dat | 栈op | 操作 |
---|---|---|
2 | Null | 将 2 入栈 |
2 | * | op 栈为空,将 * 入栈 |
4 2 | * | 将 4 入栈 |
* 4 2 | + | + 比 * 优先级低,将 * 弹出 |
3 * 4 2 | * + | 将 3 入栈 |
+ 3 * 4 2 | Null | 弹空 op 栈,依次进入 dat 栈 |
这时,逆序输出栈 datdat,得到式子的后缀表达式 2 4 * 3 +
。
经过尝试和拼凑,我们发现:
-
sisi 为数字时,直接压进 datdat 栈。
-
sisi 为运算符时,优先级若比 opop 栈栈顶符号高(是 >> 而不是 ≥≥,可以自己模拟看看),就压进栈,否则就弹出 opop 的栈顶元素到 datdat 栈里,直到比栈顶符号优先级高或栈空。
-
最后,将 opop 栈里的剩余元素弹出到 datdat 栈。
上面这些是最基本的情况。但如果有括号呢?
3.1.3 特殊情况
当 sisi 为左括号时,可以直接压进 opop 栈里。当 sisi 为右括号时,一直弹出 opop 栈栈顶到 datdat 里,直到栈顶为左括号,再弹出左括号。这些也可以通过模拟得出答案。
看例子:2 + (3 + 4) * 3
栈 dat | 栈 op | 操作 |
---|---|---|
2 | Null | 将 2 压进栈 |
2 | + | op 栈空,将 + 压进栈 |
2 | ( + | 将左括号压进栈 |
3 2 | ( + | 将 3 压进栈 |
3 2 | + ( + | + 比 ( 优先级高,将 + 压进栈 |
4 3 2 | + ( + | 将 4 压进栈 |
+ 4 3 2 | + | 将右括号与左括号之间的 + 弹出到 dat 栈 |
+ 4 3 2 | * + | * 比 + 优先级高,将 * 压进栈 |
3 + 4 3 2 | * + | 将 3 压进栈 |
+ * 3 + 4 3 2 | Null | 将 op 栈里的元素压进 dat 栈 |
最后逆序输出 datdat 栈,得到:2 3 4 + 3 * +
。
当然,我们不能忽略题目中特殊的乘方运算。模拟样例 2:2 ^ 2 ^ 3
。如下:
栈 dat | 栈 op | 操作 |
---|---|---|
2 | Null | 将 2 压进栈 |
2 | ^ | op 栈空,将 ^ 压进栈 |
2 2 | ^ | 将 3 压进栈 |
2 2 | ^ ^ | ^ 与 ^相同,将 ^ 压进栈(特殊) |
3 2 2 | ^ ^ | 将 3 压进栈 |
^ ^ 3 2 2 | Null | 将 op 栈里的元素压进 dat 栈 |
我们得到:2 2 3 ^ ^
所以,当 sisi 为乘方运算符时且 opop 栈栈顶也是乘方运算符时,也可直接压进栈中。
3.1.4 表达式输出
如果你用的是数组版栈,直接遍历即可。但如果你跟我一样用的是 STL 版栈,可以把栈 opop 用来临时存放数据。先把 datdat 里的元素全部压进 opop 栈,再倒回来,同时输出字符即可。输出记得换行。这样,第一问就结束了。
3.2 第二问
第二问相对简单,我们同样可以定义 calccalc 函数完成第二问。
3.2.1 基本运算
为了方便,定义函数用来计算两个数的计算结果。这个很简单,代码如下:
int js(int x,int y,char t)//x和y为计算的两个数,t为运算符
{
switch(t)
{
case '+':return x+y;
case '-':return x-y;
case '*':return x*y;
case '/':return x/y;
case '^':return pow(x,y);
default:return -0x3f3f3f3f;//程序不会执行到这里,同样为了保险
}
}
3.2.2 后缀表达式计算
定义两个栈 numnum 与 dat2dat2,numnum 存储计算过程,dat2dat2 用来临时存放数据,之前的 datdat 和 opop 可以继续使用。
把 datdat 全部弹出到 opop 里,接下来进行计算。
-
变量 tt 获取 opop 栈顶,opop 弹出。
-
若 tt 为数字,减去
'0'
再进入 numnum。若 tt 为运算符,弹出 numnum 栈顶 22 个元素并记录,将运算结果压进栈,同时输出过程。 -
重复 1 和 2 两个步骤,直到 opop 为空。
3.2.3 过程输出
输出过程时,反序输出 numnum,正序输出 opop。
同样利用 dat2dat2 与 datdat,反序与正序差不多,只是前者在倒回来时输出,后者在倒出去时输出。最后记得换行。
四、代码
#include <bits/stdc++.h>
using namespace std;
stack<char> dat,op;
stack<int> num,dat2;
int check(char c)
{
switch(c)
{
case '+':return 1;
case '-':return 1;
case '*':return 2;
case '/':return 2;
case '^':return 3;
case '(':return 0;
case ')':return 0;
default:return -1;
}
}
int js(int x,int y,char t)
{
switch(t)
{
case '+':return x+y;
case '-':return x-y;
case '*':return x*y;
case '/':return x/y;
case '^':return pow(x,y);
default:return -0x3f3f3f3f;
}
}
void change(string s)
{
int len=s.size();
for(int i=0;i<len;i++)
{
if(isdigit(s[i]))dat.push(s[i]);
else if(s[i]=='(')op.push(s[i]);
else if(s[i]==')')
{
char t=op.top();
while(t!='(')
{
op.pop();
dat.push(t);
t=op.top();
}
op.pop();//要弹出左括号
}
else if(check(s[i])>=1&&check(s[i])<=3)//为运算符
{
if(!op.empty())
{
char t=op.top();
while(!op.empty()&&check(s[i])<=check(t))
{
if(check(s[i])==check(t)&&s[i]=='^')break;//在s[i]与栈顶都是^号时也能进栈
op.pop();
dat.push(t);
if(!op.empty())t=op.top();
}
}
op.push(s[i]);
}
}
while(!op.empty())
{
char t=op.top();
op.pop();
dat.push(t);
}
while(!dat.empty())
{
char t=dat.top();
dat.pop();
op.push(t);
}
while(!op.empty())
{
char t=op.top();
cout<<t<<' ';
op.pop();
dat.push(t);
}
cout<<endl;
}
void calc()
{
while(!dat.empty())
{
char t=dat.top();
dat.pop();
op.push(t);
}
while(!op.empty())
{
char t=op.top();
op.pop();
if(isdigit(t))num.push(t-'0');
else
{
int x=num.top();
num.pop();
int y=num.top();
num.pop();
num.push(js(y,x,t));//传参数时要把x和y反过来
while(!num.empty())
{
int t=num.top();
num.pop();
dat2.push(t);
}
while(!dat2.empty())
{
int t=dat2.top();
cout<<t<<' ';
dat2.pop();
num.push(t);
}
while(!op.empty())
{
char t=op.top();
cout<<t<<' ';
op.pop();
dat.push(t);
}
while(!dat.empty())
{
char t=dat.top();
dat.pop();
op.push(t);
}
cout<<endl;
}
}
}
int main()
{
string s;
cin>>s;
change(s);
calc();
return 0;
}
P.S. 表格不知道咋回事,为什么没有线啊……
时间复杂度
小明正在学习一种新的编程语言 A++A++,刚学会循环语句的他激动地写了好多程序并给出了他自己算出的时间复杂度,可他的编程老师实在不想一个一个检查小明的程序,于是你的机会来啦!
下面请你编写程序来判断小明对他的每个程序给出的时间复杂度是否正确。
A++A++ 语言的循环结构如下:
Copy
F i x y
循环体
E
其中 F i x y
表示新建变量 ii(变量 ii 不可与未被销毁的变量重名)并初始化为 xx,然后判断 ii 和 yy 的大小关系,若 ii 小于等于 yy 则进入循环,否则不进入。
每次循环结束后 ii 都会被修改成 i+1i+1,一旦 ii 大于 yy 终止循环。
xx 和 yy 可以是正整数(xx 和 yy 的大小关系不定)或变量 nn。
nn 是一个表示数据规模的变量,在时间复杂度计算中需保留该变量而不能将其视为常数,该数远大于 100100。
E
表示循环体结束。
循环体结束时,这个循环体新建的变量也被销毁。
注:本题中为了书写方便,在描述复杂度时,使用大写英文字母 O
表示通常意义下 Θ
的概念。
输入格式
输入文件第一行一个正整数 tt,表示有 tt(t≤10t≤10)个程序需要计算时间复杂度。
每个程序我们只需抽取其中 F i x y
和 E
即可计算时间复杂度。注意:循环结构允许嵌套。
接下来每个程序的第一行包含一个正整数 LL 和一个字符串,LL 代表程序行数,字符串表示这个程序的复杂度,O(1)
表示常数复杂度,O(n^w)
表示复杂度为 nwnw,其中 ww 是一个小于 100100 的正整数(输入中不包含引号),输入保证复杂度只有 O(1)
和 O(n^w)
两种类型。
接下来 LL 行代表程序中循环结构中的 F i x y
或者 E
。
程序行若以 F
开头,表示进入一个循环,之后有空格分离的三个字符(串)i x y
,其中 ii 是一个小写字母(保证不为 nn),表示新建的变量名,xx 和 yy 可能是正整数或 nn,已知若为正整数则一定小于 100100。
程序行若以 E
开头,则表示循环体结束。
输出格式
输出文件共 tt 行,对应输入的 tt 个程序,每行输出 Yes
或 No
或者 ERR
,若程序实际复杂度与输入给出的复杂度一致则输出 Yes
,不一致则输出 No
,若程序有语法错误(其中语法错误只有: ①① FF 和 EE 不匹配 ②② 新建的变量与已经存在但未被销毁的变量重复两种情况),则输出 ERR
。
注意:即使在程序不会执行的循环体中出现了语法错误也会编译错误,要输出 ERR
。
数据范围
L≤100L≤100
输入样例:
Copy
8
2 O(1)
F i 1 1
E
2 O(n^1)
F x 1 n
E
1 O(1)
F x 1 n
4 O(n^2)
F x 5 n
F y 10 n
E
E
4 O(n^2)
F x 9 n
E
F y 2 n
E
4 O(n^1)
F x 9 n
F y n 4
E
E
4 O(1)
F y n 4
F x 9 n
E
E
4 O(n^2)
F x 1 n
F x 1 10
E
E
输出样例:
Copy
Yes
Yes
ERR
Yes
No
Yes
Yes
ERR
#include <iostream>
#include <cstring>
#include <algorithm>
#include <stack>
using namespace std;
const int N = 110;
string codes[N];
int get(string x, string y)
{
if (x == "n" && y == "n") return 0;
else if (x == "n") return -1;
else if (y == "n") return 1;
else if (stoi(x) <= stoi(y)) return 0;
else return -1;
}
string calc(int L)
{
int res = 0;
stack<int> stk;
string vars;
for (int i = 0; i < L; i ++ )
{
auto& s = codes[i];
if (s[0] == 'F')
{
char var[2], x[4], y[4];
sscanf(s.c_str(), "F %s %s %s", var, x, y);
if (vars.find(var) != -1) return "ERR";
vars += var;
int t = get(x, y);
if (stk.empty()) stk.push(t), res = max(res, t);
else
{
int top = stk.top();
if (top == -1 || t == -1) stk.push(-1);
else stk.push(top + t), res = max(res, top + t);
}
}
else
{
if (stk.empty()) return "ERR";
stk.pop();
vars.pop_back();
}
}
if (stk.size()) return "ERR";
if (!res) return "O(1)";
return "O(n^" + to_string(res) + ")";
}
int main()
{
int T;
cin >> T;
while (T -- )
{
int L;
string str;
cin >> L >> str;
getchar();
for (int i = 0; i < L; i ++ ) getline(cin, codes[i]);
string res = calc(L);
if (res == "ERR") cout << res << endl;
else if (res == str) cout << "Yes" << endl;
else cout << "No" << endl;
}
return 0;
}
算法时间复杂度
P5698 [CTSC1998] 算法复杂度 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
CTSC1998 D1T3
我们在编程时,最关心的一个问题就是算法的时间复杂度。但是分析一个程序的复杂度是一项很困难的工作,在程序的码风不是很好的情况下更是如此。
所以,专门研究算法的 SERKOI 小组决定开发出一个分析程序时间复杂度的软件。由于这是一个全新的领域,所以 SERKOI 小组决定先从简单情况入手进行分析。
题目描述
为了简化问题,程序只包含循环和顺序结构,程序的结构定义如下:
begin <statement> endbegin <statement> end
一个语句块的结构是递归定义的,如下所示:
loop x <statement> endloop x <statement> end
或者 op <statement>op <statement>
或者为 break <statement>break <statement>
或者为 continue <statement>continue <statement>
语句块可以为空。
注意:
-
一个程序都是以 beginbegin 开始,以相应的 endend 结束;
-
loop x <statement> endloop x <statement> end 表示其中的语句重复执行 xx 次;
-
op xop x 表示执行 xx 个单位操作;
-
上面两点中的 xx 可以是一个正整数或 nn;
-
breakbreak 语句的作用是跳出这一层循环, continuecontinue 语句的作用是跳过这一层循 环的其它语句,直接进入下一次循环。如果它(breakbreak 或 continuecontinue)不在任何一层循环中,请忽略它们。
你需要写一个程序,用来求出题目描述的程序的时间复杂度,并以多项式的形式输出。
注意,该多项式是关于 nn 的多项式,而且,常数项和系数不能省略。
数据保证能求出该程序的时间复杂度。
输入格式
输入中包含一个完整的程序, 每两条语句之间至少用一个空格或换行符隔开。
输出格式
将计算出的时间复杂度多项式按降幂排列输出。
输入输出样例
输入 #1复制
begin loop n loop 3 loop n op 20 end end end loop n op 3 break end loop n loop n op 1 break end end end
输出 #1复制
60n^2+n+3
输入 #2复制
begin op n loop 3 op n break end loop n loop n op 1 continue op n end end end
输出 #2复制
n^2+2n
说明/提示
循环的嵌套最多不超过 2020 层。
保证最终时间复杂度多项式每项的系数不超过 109109。
题解
本题的循环仅存在数字和 nn 两种情况,并且不存在判断语句,因此我们可以将整个程序转换为一个表达式:
-
对于 opop 操作,我们可以看做在这个位置放置了一个数字(操作次数)。同时在它前面加一个加号。
-
对于 continuecontinue 和 breakbreak 操作,每当我们读取到它们时,就忽略接下来的所有运算,直到碰到当前循环的 endend 语句。
-
对于 looploop 语句(和 beginbegin 语句),我们在该出放置一个左括号,同时在左括号前边放一个加号、后边放一个数字 00。如果对应的循环结构内存在 breakbreak,那么在左括号之前放置一个数字 11;否则放置 looploop 语句执行的次数(数字或者 nn)。
-
对于 endend 语句,直接放置一个右括号,即可和对应 looploop 语句的左括号匹配。
然后……对其中缀表达式求值,就做完了。以下我们举个例子(使用了样例):
具体操作的时候,就直接使用两个栈进行中缀表达式求值。(更详细的可以看这篇)。其中一个栈维护处理到的数字,另外一个栈维护处理到的运算符。数字入数字栈;每当碰到运算符时,不断取出运算符栈的栈顶,若优先级不小于当前的运算符,则不断取出数字栈栈顶的两个数字,运算后再推入数字栈,最后再将该运算符入栈;对于左括号直接入栈,碰到右括号则不断取运算符栈栈顶进行运算,知道碰到对应的左括号。
参考代码
#include<bits/stdc++.h>
#define up(l,r,i) for(int i=l,END##i=r;i<=END##i;++i)
#define dn(r,l,i) for(int i=r,END##i=l;i>=END##i;--i)
using namespace std;
typedef long long i64;
const int INF =2147483647;
struct Node{
vector<i64> A; Node(){} Node(vector <i64> _A){A=_A;}
Node operator +(const Node &t){
Node r; r.A.resize(max(A.size(),t.A.size()));
for(int i=0;i<r.A.size();++i)
r.A[i]+=(i<A.size()?A[i]:0)+(i<t.A.size()?t.A[i]:0); return r;
}
Node operator *(const Node &t){
Node r; r.A.resize(A.size()+t.A.size()-1);
for(int i=0;i<A.size();++i)
for(int j=0;j<t.A.size();++j)
r.A[i+j]+=A[i]*t.A[j]; return r;
}
void wrt(){
bool h=true; dn((int)A.size()-1,0,i) if(A[i]){
if(!h) cout<<"+"; h=false;
if(A[i]!=1||i==0) cout<<A[i];
if(i==1) cout<<"n" ; else
if(i!=0) cout<<"n^"<<i;
}
if(h) cout<<"0"; cout<<endl;
}
};
string x,y; stack<int> T; stack<Node> S; int n,f,g;
int main(){
ios_base::sync_with_stdio(false); T.push(0);
do{
cin>>x;
if(x=="begin"){
T.push(0); S.push(Node({1})),S.push(Node({0}));
} else
if(x=="end"){
if(g>1){--g;continue;}
while(T.top()!=0){
Node a=S.top(); S.pop();
Node b=S.top(); S.pop();
S.push(a+b),T.pop();
}
Node a=S.top(); S.pop();
Node b=S.top(); S.pop();
if(f==2) S.push(a); else S.push(a*b);T.pop(),f=0,--n;
} else if(x=="loop"){
if(f) {++g;continue;}
cin>>y; T.push(1),T.push(0),++n;
if(y=="n"||y=="0") S.push(Node({0,1}));
else S.push(Node({atoi(y.c_str())}));
S.push(Node({0}));
} else if(!f&&x=="op"){
cin>>y; T.push(1);
if(y=="n"||y=="0") S.push(Node({0,1}));
else S.push(Node({atoi(y.c_str())}));
}
else if(!f&&x=="continue"&&n!=0) f=1,g=1;
else if(!f&&x=="break" &&n!=0) f=2,g=1;
}while(T.size()>1);
S.top().wrt();
return 0;
}
#include <bits/stdc++.h>
using namespace std;
const int N=22;
struct Poly
{
int a[N];
Poly(){for(int i=0;i<N;i++)a[i]=0;}
};
Poly f(int k,bool b)
{
Poly P;
string s;
while(cin>>s&&s!="end")
{
if(s=="loop")
{
cin>>s;
Poly T=f(s=="n"?0:stoi(s),1);
for(int i=0;i<N;i++)P.a[i]+=T.a[i];
}
else if(s=="op")
{
cin>>s;
if(s=="n")P.a[1]++;
else P.a[0]+=stoi(s);
}
else if((s=="continue"||s=="break")&&b)
{
if(s=="break")k=1;
int t=1;
while(t)
{
cin>>s;
if(s=="loop")t++;
else if(s=="end")t--;
}
break;
}
}
if(k)for(int i=0;i<N;i++)P.a[i]*=k;
else{for(int i=N-1;i>0;i--)P.a[i]=P.a[i-1];P.a[0]=0;}
return P;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
string s;
cin>>s;
Poly ans=f(1,0);
bool b=0;
for(int i=N-1;i>=0;i--)
{
if(ans.a[i]==0)continue;
if(b)cout<<'+';
if(i==0||ans.a[i]!=1)cout<<ans.a[i];
if(i>1)cout<<"n^"<<i;
else if(i==1)cout<<'n';
b=1;
}
if(!b)cout<<0<<'\n';
return 0;
}
化学方程式
kruskal的加法
P1557 Kruscal的加法 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
奶牛 Kruscal 认为人类的加法太落后了。比如说有时候想要用加法计算 +15*3
,只能写成 +15+15+15
,真是浪费精力啊!于是,Kruscal 决定开发出一种新的加法算式。
题目描述
当然,新的算式也是建立在原本算式的基础上的,不同就在于上式可以直接写成 +++15
,同理,对于 -15*3
这样的算式可以写成 ---15
。一段时间后,Kruscal 又被那无穷多个 +-
号囧到了,于是他又将这个算式改进了一下。
比如 +15*3
又可以写成 +(3)15
,同样,-15*3
等价于 -(3)15
。但从上面可以看出,对于乘数较小的情况,如 +++15
这样的表述还是很方便的,于是在新的算式中还是保留了这种形式。
对于算式还要做一点特殊的说明:+15*3
转换成 Kruscal 型算式时可以写成 +++15
或 +(3)15
,但不可以写成 ++(2)15
这样的形式。
对于算式 23+15*3-2
可以表示为以下几种形式:23+++15-2
、23+(3)15-2
、+23+++15-2
、+23+(3)15-2、+(1)23+(3)15-(1)2
。不会出现如下几种形式:(1)23+++15-2
、+23++(2)15-(1)2
、23+++15-2+(0)100
、23-(-3)15-2
。
输入格式
一行,一个 Kruscal 型算式。
输出格式
一行,为运算结果。
输入输出样例
输入 #1复制
+(1)23+(3)15-(1)2
输出 #1复制
66
说明/提示
对于 100%100% 的数据,算式长度不超过 20002000。
首先读题,通过题目我们可以得知:
- 算式只存在 '+' , '-' , '*' 的运算且不用考虑运算优先级
- 不会出现连乘
发现不用考虑优先级后我的第一想法是
将算式拆分成一些小的单一算式( 运算符 + 数字 )再计算
如: 处理 str="+++1++2+(3)3" :
- 找到在上一个运算符号后重新出现的第一个运算符号位置 i=0
- 从找到的运算符位置起找到 前一位不是+或-的 运算符的位置 j=4
- str[i]~str[j] (不含str[j]) 即为一组单一算式,计算其结果并叠加到ans上
- 重复以上操作
拆分代码:
// 因为要靠运算符进行标识,所以需要在前后端添加符号
scanf("%s", &(str[1]));// 直接从第一位开始输入
if (str[1] != '+' && str[1] != '-')str[0] = '+';// 避免重复
else str[0] = ' ';
int len = strlen(str);
str[len] = '+';
//str[len+1]='\0';// 在全局定义的数组默认初始化为0
for (int i = 0; i < len; i++)
{
if (str[i] == '+' || str[i] == '-') // 找到运算符
{
for (int j = i + 1; j < len; j++) // 开始找结束位置
{
if (str[j] == '+' || str[j] == '-')
{
if (str[j - 1] != '+' && str[j - 1] != '-')
{
for (int k = i; k < j; k++)stmp[k - i] = str[k];// 拷贝这一段字符串
stmp[j - i] = 0;
// TODO : 处理这一段算式
i = j - 1; // 跳到下一段
break;
}
}
}
}
}
然后处理这一小段代码:
int GetVal(const char* str)
{
int num = 0, plus = 0, minus = 0;
// num存括号外的数,plus和minus分别存储加(减)次数
int len = strlen(str);
int tmp = 0, tmpn = 0;
// tmp标记是在记录括号内的数还是要计算的数
for (int i = 0; i < len; i++)
{
if (str[i] == '+')plus++;
else if (str[i] == '-')minus++;
else if ('0' <= str[i] && str[i] <= '9')
{
// 计算数字
if (tmp)
{
tmpn = tmpn * 10 + str[i] - '0';
}
else
{
num = num * 10 + str[i] - '0';
}
}
else if (str[i] == '(')// 标记
{
tmp = 1;
}
else if (str[i] == ')')
{
tmp = 0;
}
}
if (tmpn)tmpn--;// 如果有括号中的数,则减去括号前的符号所代表的一次
if (plus)
{
plus += tmpn;// 叠加
return num * plus;
}
else
{
minus += tmpn;// 叠加
return -num * minus;
}
}
(然后愉快的40分了)
这个时候瞄到题目下方说的 "算式长度不超过2000" 就很明显要使用高精度
(我看Dalao们都是自己写了一个类,不过我比较弱,只能写个结构体来代替) 高精度几乎全忘光的我写了将近一天...
typedef struct _StrNumber
{
char num[8848];
int flag; // 是否有负号
}LLL;
void pluss(char* numA, char* numB); // 加法 将结果存入numA
void minuss(char* numA, char* numB, char* ans); // 减法,将结果存入ans
void cf(char* numA, char* numB); // 乘法(忘了英文怎么拼),将结果存入numA
void LLLcmp(char* a,char* b); // 比较,返回值类似strcmp
// 实现:
/*高精度加法 将结果存入numA*/
void pluss(char* numA, char* numB)
{
int arr1[2008] = { 0 }, arr2[2008] = { 0 }, len, len1, len2, i, j;
len1 = strlen(numA);
for (i = 0; i < len1; i++)//字符串倒叙导入数组,方便从低位开始运算
arr1[i] = numA[len1 - 1 - i] - '0';
len2 = strlen(numB);
for (i = 0; i < len2; i++)
arr2[i] = numB[len2 - 1 - i] - '0';//字符串中的是字符,要转换成数字
len = (len1 > len2) ? len1 : len2;
for (i = 0; i < len; i++)
{
arr1[i + 1] += j = (arr1[i] + arr2[i]) / 10;//进位
arr1[i] = (arr1[i] + arr2[i]) % 10;//保留
}
if (j)len++;//最后一位是否进位
for (i = 0; i < len; i++)
numA[i] = arr1[len - 1 - i] + '0';
//printf("%d", arr1[len - 1 - i]);
}
/*减法*/
char tmp[2008] = { 0 };
int arr1[2008] = { 0 }, arr2[2008] = { 0 };
void minuss(char* numA, char* numB, char* ans)
{
memset(tmp, 0, 2008);
memset(arr1, 0, 2008*4);
memset(arr2, 0, 2008*4);
int len = 0, len1 = strlen(numA), len2 = strlen(numB);
int i, j;
int flag = 0;
len1 = strlen(numA), len2 = strlen(numB);
for (i = 0; i <= len1 - 1; i++)arr1[len1 - i] = numA[i] - '0';
for (i = 0; i <= len2 - 1; i++)arr2[len2 - i] = numB[i] - '0';
i = 1;
while (i <= len1 || i <= len2)
{
if (arr1[i] < arr2[i])
{
arr1[i] += 10;
arr1[i + 1]--;
}
tmp[i] = arr1[i] - arr2[i] + '0';
i++;
}
len = i;
while ((tmp[len - 1] == '0') && (len >= 0))len--;
j = 0;
for (i = len - 1; i >= 0; i--)
{
ans[j] = tmp[i];
//printf("'%c\n", tmp[i]);
j++;
}
}
int a[2008] = { 0 }, b[2008] = { 0 }, c[4016] = { 0 };
void cf(char* numA, char* numB)
{
int lena, lenb, lenc, i, j, x;
memset(c, 0, 4016 * sizeof(int));
lena = strlen(numA); lenb = strlen(numB);
for (i = 0; i < lena; i++) a[lena - i-1] = numA[i] - '0';
for (i = 0; i < lenb; i++) b[lenb - i-1] = numB[i] - '0';
for (i = 0; i < lena; i++)
{
x = 0;
for (j = 0; j < lenb; j++)/*对乘数的每一位进行处理*/
{
c[i + j] = a[i] * b[j] + x + c[i + j];/*当前乘积+上次乘积进位+原数*/
x = c[i + j] / 10;
c[i + j] %= 10;
}
c[i + lenb] = x;
}
lenc = lena + lenb;
while (c[lenc - 1] == 0 && lenc > 1)/*删除前导0*/
lenc--;
j = 0;
for (i = lenc - 1; i >= 0; i--)
{
//printf("%d_", c[i]);
numA[j] = c[i] + '0', j++;
}
}
int LLLcmp(char* a, char* b)
{
// -1:a小,0:相等,1:b小
int len1 = strlen(a), len2 = strlen(b);
if (len1 < len2)return -1;
else if (len1 > len2)return 1;
for (int i = 0; i < len1; i++)
{
if (a[i] < b[i])return -1;
else if (a[i] > b[i])return 1;
}
return 0;
}
为了适配高精度,代码也要改动亿下
// 求出str算式的值
void GetVal(const char* str)
{
char plus[2008] = {0}, minus[2008] = { 0 };
/*plus 和 minus 分别表示加(减)几次*/
long long len = strlen(str);
/*
* flag 标识当前计算的是括号内的数字还是括号外的
* num 储存括号外的数 tmpn 储存括号内的数
*/
long long flag = 0;
char num[2008] = { 0 }, tmpn[2008] = { 0 };
for (int i = 0; i < len; i++)
{
if (str[i] == '+')
{
pluss(plus, "1");
}
else if (str[i] == '-')
{
pluss(minus, "1");
}
else if (str[i] == '(')
{
flag = 1;
}
else if (str[i] == ')')
{
flag = 0;
}
else if ('0' <= str[i] && str[i] <= '9')
{
/*计算数字*/
if (flag)
{
//tmpn = tmpn * 10 + str[i] - '0';
int e = strchr(str, ')') - str;
// 找到右括号的位置,直接复制
for (int j = i; j < e; j++)
{
tmpn[j - i] = str[j];
}
flag = 0;
i = e;
}
else
{
//num = num * 10 + str[i] - '0';
for (int j = i; j < len; j++)
{
num[j - i] = str[j];
}
break;
}
}
}
if (tmpn[0])minuss(tmpn, "1", tmpn);
if (plus[0])/*如果是加*/
{
pluss(plus, tmpn);
cf(num, plus);
if (ans.flag == 0)
{
// 如果原数是正数,就直接加
pluss(ans.num, num);
}
else
{
// 否则是负数
int tmp = LLLcmp(ans.num, num);
if (tmp == 0)
{
// 如果相同相加就是0
ans.flag = 0;
ans.num[0] = '0';
ans.num[1] = '\0';
}
else if (tmp > 0)
{
// 如果num较小则还是负数,数值变为ans-num
ans.flag = 1;
minuss(ans.num, num, ans.num);
}
else if (tmp < 0)
{
// 如果原数较小则变为正数,数值变为num-ans
ans.flag = 0;
minuss(num, ans.num, ans.num);
}
}
}
else/*否则是减*/
{
pluss(minus, tmpn);
cf(num, minus);
if (ans.flag == 1)
{
// 如果是负数,则直接增加数值
pluss(ans.num, num);
}
else
{
int tmp = LLLcmp(ans.num, num);
if (tmp == 0)
{
ans.flag = 0;
ans.num[0] = '0';
ans.num[1] = '\0';
}
else if (tmp > 0)
{
// 减数小于被减数,仍是正数
ans.flag = 0;
minuss(ans.num, num, ans.num);
}
else if (tmp < 0)
{
// 被减数小于减数,变为负数
ans.flag = 1;
minuss(num, ans.num, ans.num);
}
}
}
}
rexp
P3719 [AHOI2017初中组] rexp - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
题目背景
为了解决形形色色的字符串匹配问题,正则表达式是一个强有力的工具。正则表达式通过定义一套符号体系,能够表示出需要查找的字符串所具有的性质。如 a|aa
能匹配 a
或 aa
,(a|b)c
能匹配 ac
或 bc
。
题目描述
完整的正则表达式过于复杂,在这里我们只考虑由 (
、)
、|
和 a
组成的正则表达式。运算遵循下列法则:
-
有括号时,我们总是先算括号内的部分;
-
当两个字符串(或由括号定义的子串)间没有符号时,我们总把它们连起来作为一个整体;
-
|
是或连接符,表示两边的字符串任取其一,若同一层里有多个或连接符,可以看作在这些或连接符所分开的若干字符串里任取其一。
例如,(aaa)aa|aa|(a(aa)a)
、(aaaaa)|(aa)|aaaa
和 aaaaa|aaaa|aa
是等价的,它们都能匹配长度为 2,42,4 或 55 的全 a
字符串。
下面给定一个简化正则表达式,试编程计算它最多能匹配多长的全 a
字符串。
输入格式
输入一行一个合法的简化正则表达式。
输出格式
一行一个整数,表示能匹配的最长全 a
字符串长度。
输入输出样例
输入 #1复制
(aaa)aa|aa|(a(aa)a)
输出 #1复制
5
输入 #2复制
((a|aaa)|aa)|a
输出 #2复制
3
输入 #3复制
(a(aa|aaa)a|(a|aa))aa
输出 #3复制
7
说明/提示
【数据范围】
对于 20%20% 数据,表达式长度不超过 100100,且不存在括号。
对于 40%40% 数据,表达式长度不超过 100100。
对于 70%70% 数据,表达式长度不超过 2×1032×103。
对于 100%100% 的数据,表达式长度不超过 105105。
保证表达式合法(即 |
两端和括号内运算结果均非空字符串)。
wa50,当遇见符号为“|”时,应当直接返回,而不是继续循环,因为左半边的计算已经停止了。
#include<bits/stdc++.h>
using namespace std;
int p(int j)
{
char c;
while(scanf("%c",&c)!=EOF)
{
if(c=='a') j++;
if(c=='(') j=j+p(0);
if(c=='|') return max(j,p(0));
if(c==')') return j;
}
return j;
}
int main()
{
cout<<p(0);
return 0;
}
题目传送门
这题是典型的递归题,这题其实是比较水的,因为只有'a'单种字符,不需要过多地处理,有一道类似的递归题推荐做一下
P1928外星密码
tips
没有说明长度的读入可以直接用while(cin>>s)
这种方式读入,因为cin
和scanf
在没有读取到数据时会返回文件结束符EOF,若读取到数据则会返回读取到几个数据,比如cin>>a>>b
如果 aa 和 bb 都成功读取会返回2
思路
因为括号和|
可以算是运算符,所以遇到他们就递归解决然后return结果,即把每个运算符看成单独的式子解决,把大问题分解为小问题(递归思想),具体注释在代码里,其实其他dalao的题解已经解释的很详细了
代码
#include<bits/stdc++.h>
using namespace std;
int work()
{
int s = 0;//因为字符都是'a'就直接用数字表示个数就行
char ch;
while(cin >> ch)
{
if(ch == ')')//括号结束,返回递归结果
return s;
else if(ch == '(')//括号开始,当前字符数加上括号的结果
s += work();
else if(ch == '|')//判断左右,因为出现|时左边已经计算完毕,所以要return结果
return max(s, work());
else//是'a'就字符数++
s++;
}
return s;
}
int main()
{
cout<<work();//递归解决
}
外星密码
P1928 外星密码 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
梯度求解
比较简单的一道题,只需要把其他变量对应的数代入进去,在栈里存表达式的要求的偏导的系数和对应的指数就可以,最后就变成处理关于偏导变量的一元多次方程的导数,就比较简单了。比赛的时候没有调出来,好亏。
这种解法对于变量无关的求解是可行的,实际上利用二叉树的方法更好。
附大佬链接
第31次CCF计算机软件能力认证 - ~Lanly~ - 博客园 (cnblogs.com)
#include <bits/stdc++.h>
using namespace std;
const int N = 1005;
const int mod = 1e9 + 7;
using ll = long long;
ll coef[N];
int n, m, to_solve;
string s, tmp;
vector<string> vec;
void solve() {
stack<map<ll, ll>> expr;
for (auto& str: vec) {
if (str.empty()) {continue;}
if (str[0] == 'x') { // var
ll index = atoi(str.substr(1).c_str());
map<ll, ll> tp;
if (index == to_solve) {
tp[1] = 1;
} else {
tp[0] = coef[index] % mod;
}
expr.push(tp);
} else if (str.size() == 1 && !isdigit(str[0])) { // operator
auto pre = expr.top(); expr.pop();
auto suf = expr.top(); expr.pop();
map<ll, ll> res;
if (str == "+") {
for(auto& p: pre) {
res[p.first] = (p.second % mod);
}
for (auto& p: suf) {
res[p.first] = (res[p.first] + p.second) % mod;
}
} else if (str == "-") {
for(auto& p: suf) {
res[p.first] = (p.second % mod);
}
for (auto& p: pre) {
res[p.first] = (res[p.first] - p.second) % mod;
}
} else {
for (auto& p: pre) {
for (auto& q: suf) {
ll zs = p.first + q.first;
ll bs = (p.second * q.second) % mod;
res[zs] = (res[zs] + bs) % mod;
}
}
}
expr.push(res);
} else { // digit
ll digit = atoi(str.c_str());
digit %= mod;
map<ll, ll> tp;
tp[0] = digit;
expr.push(tp);
}
}
assert(expr.size() == 1);
ll res = 0;
while (!expr.empty()) {
auto final_expr = expr.top();
expr.pop();
for (auto& p: final_expr) {
ll pw = 1;
ll bs = (p.second * p.first) % mod;
ll c = p.first - 1;
while (c > 0) {
c--;
pw = (pw * coef[to_solve]) % mod;
}
pw = (pw * bs) % mod;
res = (res + pw) % mod;
}
}
res %= mod;
res = (res + mod) % mod;
cout << res << '\n';
}
int main() {
cin >> n >> m;
getchar();
getline(cin, s);
stringstream ss(s);
while (getline(ss, tmp, ' ')) {
vec.push_back(tmp);
}
for (int i = 1; i <= m; i++) {
cin >> to_solve;
for (int j = 1; j <= n; j++) {
cin >> coef[j];
}
solve();
}
}
#define _CRT_SECURE_NO_WARNINGS 1
#include<bits/stdc++.h>
using namespace std;
//标记三种符号
#define CONST 1
#define X_n 2
#define OPE 3
typedef long long ll;
ll MO = 1e9 + 7;
//二叉树节点
struct oper_Node {
string val;
//表示类型
int type;
oper_Node* left = NULL;
oper_Node* right = NULL;
//构造函数
oper_Node(string x)
{
val = x;
}
};
// 计算导数,返回一个数组
vector<ll> cal(oper_Node* root);
// 将CONST 或者 X 转变为数字
ll trans_x(string a);
// 模运算
ll mod(ll x);
// 存储着变量的数值
vector<ll> val(122);
// 判断是否为偏导的变量
vector<bool> if_d(122,false);
int main()
{
int n, m;
scanf("%d %d", &n, &m);
string s;
//scanf使用之后会再缓冲区留下一个'\n' 需要我们处理
getchar();
//getline 读入字符串
getline(cin,s);
stack<oper_Node*> p;
int i = 0;
// 用栈来处理表达式
while (true)
{
string temp;
//注意边界情况,不要越界
while (s[i]!= '\0' && s[i] != ' ')
{
temp += s[i++];
}
//不是操作数
if(temp!="*" && temp!= "+" && temp!="-")
{
oper_Node* newnode = new oper_Node(temp);
if (temp[0] == 'x')
{
newnode->type = X_n;
}
else
{
newnode->type = CONST;
}
p.push(newnode);
}
//操作数
else
{
oper_Node* a = p.top();
p.pop();
oper_Node* b = p.top();
p.pop();
oper_Node* newnode = new oper_Node(temp);
newnode->type = OPE;
newnode->left = b;
newnode->right = a;
p.push(newnode);
}
if (s[i] == '\0')
{
break;
}
i++;
}
//最后剩余就是二叉树根节点
oper_Node* exp = p.top();
for (int i = 0; i < m; i++)
{
int x;
scanf("%d", &x);
char a = x + '0';
string target = "x";
target += a;
//if_d 判断是否需要求导
if_d[x] = true;
for (int j = 1; j <= n; j++)
{
scanf("%lld", &val[j]);
}
vector<ll> end = cal(exp);
printf("%lld\n",mod(end[0]));
//恢复标记
if_d[x] = false;
}
return 0;
}
vector<ll> cal(oper_Node* root)
{
// 常数的导数为0
if (root->type == CONST)
{
vector<ll> temp(2);
temp[0] = 0;
temp[1] = trans_x(root->val);
return temp;
}
//若为x看是否为变量,变量求导为1,常量为0
if (root->type == X_n)
{
string a = root->val;
int i = 1;
int x = 0; // X_n]
vector<ll> temp(2);
while (a[i] != '\0')
{
x = x * 10 + a[i] - '0';
i++;
}
if (if_d[x])
{
temp[0] = 1;
temp[1] = val[x]%MO;
}
else
{
temp[0] = 0;
temp[1] = val[x]%MO;
}
return temp;
}
//若为操作数 需要计算两侧的导数与两侧的计算结果
vector<ll> a = cal(root->left);
vector<ll> b = cal(root->right);
vector<ll> result(2);
if (root->val == "+")
{
result[0] = (a[0] + b[0])%MO;
result[1] = (a[1] + b[1])%MO;
return result;
}
else
if (root->val == "-")
{
result[0] = (a[0] - b[0])%MO;
result[1] = (a[1] - b[1])%MO;
return result;
}
else
{
result[0] = ((b[1] * a[0])%MO + (a[1] * b[0])%MO)%MO;
result[1] = (a[1] * b[1])%MO;
return result;
}
}
//将非操作数转为相应的数字
ll trans_x(string a)
{
if (a[0] == 'x')
{
int i = 1;
int x = 0; // X_n
while (a[i] != '\0')
{
x += x * 10 + a[i] - '0';
i++;
}
return val[x];
}
else
{
ll x=0;
int i = 0;
if (a[i] != '-') {
while (a[i] != '\0')
{
x = x * 10 + a[i] - '0';
i++;
}
}
else
{
x = 0 - (a[++i] - '0');
i++;
while (a[i] != '\0')
{
x = x * 10 - a[i] + '0';
i++;
}
}
return x;
}
}
ll mod(ll x)
{
if (x < 0) x = x + MO;
return x;
}