CCF-CSP 梯度求解(2023-09)(栈与二叉树100分)

CCF-CSP 梯度求解

前言

这道题是一道模拟题,模拟计算导数。用到了栈,以及运算符二叉树的应用,总体来说比较麻烦。看了好多题解都是用次方的方式解决的,我根据官方的python部分题解写了c++版本,主要应用了递归以及二叉树的处理。

题解

混分

  • 看到最后的测试点,1到2只有一个变量,甚至连运算符都没有,3到4只有一个运算符,n同样等于1。那么我们就可以直接根据测试点来解答,只求一个运算符以及一个变量的情况(40分)

解题思路

构建二叉树
  • 首先我们要读入字符串,随后处理字符串
  • 然后是处理波兰表达式的方法:由题目所说,我们当碰到运算符的时候,出栈两个元素进行运算,并且将计算结果压入栈之中。
  • 由于我们最后的目的是要构造出来相应的运算二叉树(叶子节点为变量或者是常数,非叶子节点是操作符)
  • 所以我们可以转变为对节点的操作:当遇到变量或者常数的时候,创建节点,并且将节点压入栈之中。当遇到操作数的时候,先创建节点(当前的操作数),出栈两个节点,将三个节点组成二叉树的形式,再将根节点也就是操作符节点压入栈之中即可,重复以上操作,直到处理完成。得到我们的二叉树
处理二叉树(计算导数)
  • 二叉树如图所示
*
X1
X2

表示的是X1 * X2

  • 最简单的情况之下: 若是常数(除去求偏导的变量外的数),导数为0 , 所需要求的偏导变量为1;
  • 处理二叉树,根据导数公式,我们根据不同的操作符采用不同的公式
  • 若为+ 即 前导数加后导数 d1 + d2
  • “-” :前导数减去后导数 d1 - d2
  • “*” :d1 * v2 + d2 * v1;
  • 所以完成每次导数,我们需要计算得出前后表达式的偏导结果,以及前后表达式的计算结果
  • 递归结果我们可以设置成一个二维变量,第一个数代表该表达式的偏导结果,第二个数代表该表达式的计算结果
其他注意
  • 模运算 % 当为负数时,其结果还为负数,所以需要我们进行判断处理,将其转为正数
  • int类型为最大为2的32次,但是我们计算过程之中可能出现超过这个数的情况,所以我们使用 long long 类型来进行计算

参考代码

#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;
}

  • 14
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值