梯度求解
这道题是直接在考场上面做的。当然因为去年九月的我过于稚嫩,基本的字符串操作都没有完成好。
当然这次再写我发现,我的思路和在考场上面一模一样,导致还是无法下手…
无法下手的原因就是我一直在思考应该以什么样的结构来存储以及处理输入的后序表达式。先入为主的思维就是将函数的形式写成我们直观感受到的那种,然后就会发现复杂无比…每一个表达式有哪些变元?这些变元的指数是多少?表达式的系数是多少?期间还要设计到乘法操作,而且对于最后的表达式还有加法求导、减法求导和乘法求导,觉得复杂无比然后无法下手。
这道第三题的难点在思路而不是优化,有点像登机牌那道题,就是你应该如何实现。
和之前那篇博客一样(原先博客链接:202309 CSP认证),我还是参考的这位博主的代码,思路简洁,代码简单:CSP 202309-3 梯度求解
简述一下代码的思路,也就是我觉得很厉害的地方,极大程度上简化了这个问题
- 关于求偏导的总体思路:先直接存储表达式,不做处理,每次输入一个主元后,再按照后序栈的方式处理表达式,此时将其余变元视作和常数等同,最后的表达式只含主元。 其实在求导过程中异常复杂的地方就是涉及到太多变元的运算。如果我们想整理出一个完整的表达式,也就是在输入表达式后就做出处理,此时存储和运算就会很复杂,因为我们不能丢掉任何一个变元。而按照博主的思路,在得到主元后,我们只需要关注到主元,其他的变元直接带入赋值变成常量。此时表达式会简洁很多,也就是一个表达式中只含有(非连续次的)含主元项,以及常数项。此时加法和乘法运算都会方便很多(乘法:系数相乘指数相加;加法:重复项系数相加指数不变,非重复项直接相加)
- 对表达式中每一项的存储结构:采用
map<ll, ll>
的方式存储。在第一问的基础上,此时表达式的每一项极为简洁,都可以表示为系数 x 主元^(次数)
。因此一个完整的表达式只需要包含多个pair
对,其中一个为指数(次数)一个为系数,很明显我们将次数作为key
字段。 因此一个计算式里面包含这样一些pair<指数,系数>
对,将这些pair
对用map存储
在这种思路和存储模式下该题就变得简单很多,对于存储的表达式的每项元素:
如果为变元:
- 判断是否为主元
- 如果是主元:初始化系数为1,指数为1,压入expr栈中
- 如果不为主元:初始化指数为0,系数直接带入赋值(视为常数项)
如果为三个操作符号中的一种,弹出栈顶的两个表达式
- 如果为加法 :重复项系数相加指数不变,非重复项直接相加
- 如果为减法:重复项系数相减指数不变,非重复项直接相加(注意弹出的两个表达式是哪个减去哪个)
- 如果为乘法 :遍历每个
pair
对,系数相乘指数相加
满分代码+注释如下:
#include<bits/stdc++.h>
#define key first
#define value second
using namespace std;
typedef long long ll; //为了防止计算过程中的溢出用ll存储
const int MOD = 1e9 + 7;
const int N = 110;
int n;
vector<string> vec; //存储一个表达式
int coef[N]; //存储每个变量的取值
int to_int(string str)
{
stringstream ssin(str);
int x;
ssin >> x;
return x;
}
void handle(int to_solve)
{
stack<map<ll, ll> > expr;
//该代码解决此问题的核心思路:先收到要求导的变元再处理表达式,此时在处理表达式的时候只关注需要求导的变元,其他的变元直接带入数据变成一个常数
//这样可以简化我们的存储和后续处理,只关注我们需要求导的元
//核心结构:用一个map<ll,ll> 来存储一个表达式,其中key字段是指数,也就是次数; value字段是该次数前面的系数
for(auto str : vec){
map<ll, ll> temp;
if(str[0] == 'x'){ //此时是个变元
int index = to_int(str.substr(1));
if(index == to_solve){ //如果是目标变元
temp[1] = 1; //系数为1,指数为1
}else{ //如果不是
temp[0] = coef[index] % MOD; //相当于一个常数项,指数为0,系数为其取值
}
}
else if(str == "+" || str == "-" || str == "*"){ //如果是符号
map<ll, ll> pre = expr.top(); expr.pop();
map<ll, ll> suf = expr.top(); expr.pop(); //取出两个表达式
if(str == "+"){
//两个表达式里面的内容做集合,指数相同的系数需要相加
for(auto x : pre){
temp[x.key] = x.value % MOD;
}
for(auto x : suf){
temp[x.key] = (temp[x.key] + x.value) % MOD; //若有相同指数的是系数相加
}
}else if(str == "-"){
for(auto x : suf){
temp[x.key] = (x.value) % MOD;
}
for(auto x : pre){ //注意pre是被减数
temp[x.key] = (temp[x.key] - x.value) % MOD;
}
}else{
for(auto x : pre){
for(auto y : suf){ //分别相乘
ll xs = (x.value * y.value) % MOD; //系数相乘
ll zs = (x.key + y.key); //指数相加
temp[zs] = (temp[zs] + xs) % MOD; //指数相同的部分要合一而不是覆盖
}
}
}
}
else{ //如果是常数
int con = to_int(str);
temp[0] = con % MOD; //指数为0,系数为其值
}
expr.push(temp);
}
if(expr.size() != 1) {cout << "error\n";return;} //出错了
ll res = 0; //最终结果
map<ll, ll> final_expr = expr.top(); expr.pop(); //取出最终的表达式,此时这个表达式只和指定变元相关
for(auto item : final_expr){ //对于里面的每一个pair对,其中key是对应的指数,value该次项前面的系数
//cout << item.key << " 一组数值 " << item.value << endl;
//指数放下来乘系数(变成求导后的系数),指数减一(变成求导后的指数)
ll ans = (item.value * item.key) % MOD; //key为指数,value为系数
ll zs = item.key - 1; //降次
for(int i = 0;i < zs;i ++){
ans = (ans * coef[to_solve]) % MOD;
}
res += ans;
res %= MOD;
}
res %= MOD;
res = (res + MOD) % MOD;
cout << res << endl;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int m;
cin >> n >> m;
string line, str; getline(cin, line);
getline(cin, line);
//先不对表达式做处理,直接存入
stringstream ssin(line);
while(ssin >> str){
vec.push_back(str);
}
while(m --){
int index; cin >> index;
for(int i = 1;i <= n;i ++){
cin >> coef[i];
}
handle(index); //开始求导
}
return 0;
}
官网结果:
在写题过程中在类似于62行最开始很疑惑,因为我认为访问Map里面没有的key字段是会报错的,所以我之前一直都是先调用count
方法再访问,其实不需要,我测试了以下代码:
int main()
{
map<int, int> m;
cout << m.count(1)<<endl;
cout<<m[1];
cout << endl;
cout << m.count(1);
return 0;
}
得到输出如下:
0
0
1
好了OVER,这道题现在算是写了两次了吧,还是满经典的这道题!我觉得!要掌握这种化简的思路!!!