CSP 202309-3 梯度求解 详解(60分debug到100分)

题目浅析

        题目主要需要解决,如何将逆波兰式还原为表达式并进行运用,以及如何计算整个表达式的导数,两大问题。

解决逆波兰式
要点

        根据题目提到的逆波兰式联系通过二叉树的后序遍历,通过建二叉树树将原表达式存储

详解

        定义二叉树结点时,定义id来识别该结点,并用于进行左右孩子的连接。同时,将每个结点对应的字符串存储下来。此外为了便于后续计算导数还定义了d_v表示以该节点为根的子树的表达式的导数的值,pre_v表示以该节点为根的子树的表达式, 直接带入各个数值的原始值。(这两个属性的用途后续会再解释)。

        定义结点类型的栈用于将逆波兰式还原为表达式的过程。

        开始遍历逆波兰式中以空格为分隔的每一个字符串,根据逆波兰式是表达式二叉树后续遍历的结果可得,叶子结点都是自变量或常数,运算符号一定都有左右孩子。

        据上,当遍历到的字符串是“+”或“-”或“*”某运算符号时,首先建立一个树的结点赋值id和字符串po,接着从栈中取出两个结点分别作为右孩子和左孩子,注意此处先出栈的元素作为右孩子,顺序不可交换,否则"-"时会出现错误。将左右孩子设定好之后,将该结点入栈。

        当遍历到其他字符串即自变量或常数时,建立结点,对相应属性赋值,并将结点放入栈中。

        直到最后一个字符串建立好结点,建树完成。

计算导数
要点

        计算以每个结点为根节点的子树对应的表达式的导数和原始值,根节点对应的表达式的导数即为整个表达式的结果。

 详解

        以样例1的表达式x1 *(x1 * x1 + x2)为例进行分析 计算整体的导数最终都会落实到计算局部的导数和局部的表达式的值,对原式求导得到{x1}' *(x1*x1+x2)+x1*{(x1*x1+x2)}',其他项此时可以得到,继续对{(x1*x1+x2)}'计算,得到{(x1*x1)}'+{x2}',继续对{(x1*x1)}'部分进行求导,得到{x1}'*x1+x1*{x1}',此时带入各值即可得到对原表达式求导的值。

        根据上述计算过程可以考虑在代码中实现以下过程:将每个结点处,以该节点为根的子树形成表达式的导数和原始值先计算出来,即,由于计算整体的导数最终都会落实到,计算局部的导数和局部的表达式的值,则先将局部导数和局部的表达式的值计算出来,再逐层往上最终得到整体的导数值。

        通过对表达式二叉树的深度遍历,计算出每个结点的局部表达式的导数和原始值,最终根节点处的导数值即为整体表达式的导数值。

 60分代码及debug

​
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int MOD=1e9+7;
int n,m,cnt;
string st, str;
struct nodes
{
	int id=-1;
	int l=-1;//该节点左孩子的id 为-1时则表示没有左孩子 
	int r=-1;//该节点右孩子的id 为-1时则表示没有右孩子 
	string po;//该节点自身表示的字符串 
	int d_v;//以该节点为根的子树的表达式的导数的值 
	int pre_v;//以该节点为根的子树的表达式 直接带入各个数值的原始值 
}node[240];
stack<nodes> s;
int x[110];
int xnum;
int qmi(int a,int b)
{
	a=a%MOD;
	b=b%MOD;
	int res=0;
	res=(a*b)%MOD;
	return res;
}
void dfs(int cnt)
{
	if(node[cnt].l==-1&&node[cnt].r==-1)//没有左右孩子是叶子结点 
	{//叶子结点一定是某自变量或常数 

		string temp=node[cnt].po;
		if(temp[0]=='x')//是自变量 
		{
	        //算出xi的i 
			int num=temp[1]-'0';
			

			if(num==xnum)//是设定的需要求偏导的那个自变量 
			{
				
				node[cnt].d_v=1;
				node[cnt].pre_v=x[num]; 
			}
			else//其他自变量 当做常数处理 
			{
			
				node[cnt].d_v=0;
				node[cnt].pre_v=x[num];
			}
		}
		else//是常数本身 
		{
				node[cnt].d_v=0;
				node[cnt].pre_v=atoi(temp.c_str());
		}
		return;
	}
	else
	{//不是叶子结点一定是某个符号 

		string temp=node[cnt].po;
		dfs(node[cnt].l);//遍历左右,用于下方计算出该结点子树形成的表达式的导数值和原始值 
		dfs(node[cnt].r);

		if(temp=="+")
		{
			node[cnt].pre_v=(node[node[cnt].l].pre_v+node[node[cnt].r].pre_v)%MOD;
			node[cnt].d_v=(node[node[cnt].l].d_v+node[node[cnt].r].d_v)%MOD;
		}
		else if(temp=="-")
		{
			node[cnt].pre_v=node[node[cnt].l].pre_v-node[node[cnt].r].pre_v;
			node[cnt].d_v=node[node[cnt].l].d_v-node[node[cnt].r].d_v;
		}else if(temp=="*")
		{
			int a=node[node[cnt].l].d_v;
			int b=node[node[cnt].l].pre_v;
			int c=node[node[cnt].r].d_v;
			int d=node[node[cnt].r].pre_v;
			node[cnt].pre_v=qmi(b,d);
			node[cnt].d_v=qmi(a,d)+qmi(b,c);
		}
	}
}
signed main()
{
    cin >> n >> m;
    getchar();
    getline(cin, st);
    stringstream ss(st);
    while (getline(ss, str, ' ')) {

		if(str=="+"||str=="-"||str=="*")
		{
			cnt++;//给树的结点编号从1开始 
			node[cnt].po=str;
			node[cnt].id=cnt;
			
			//从栈中取出两个元素 设为其左右孩子
			nodes temp1;
			temp1=s.top();
			s.pop();
			node[cnt].r=temp1.id;

			nodes temp2;
			temp2=s.top();
			s.pop();
			node[cnt].l=temp2.id;

			s.push(node[cnt]);

		}
		else
		{
			cnt++;
			node[cnt].po=str;
			node[cnt].id=cnt;	
			s.push(node[cnt]);		
		}
	}
	//root的id=cnt

	int root=cnt;
	while(m--)
	{
		xnum=0;
		memset(x,0,sizeof(x));
		cin>>xnum;
		for(int i=1;i<=n;i++) cin>>x[i];
		
		dfs(root);//以编号为cnt的结点为根节点遍历每个结点计算导数的值 
		int res=node[root].d_v;
		
		res=res%(MOD);
		if(res>=0)
		{
			cout<<res<<endl;			
		}
		else
		{
			res+=MOD;
			cout<<res<<endl;
		}
	}

	return 0;
}

​

        在判定某自变量是第几个自变量时,由于自变量个数会超过9个,因此直接将temp[1]-‘0’的值当做自变量xi的i值是错误的,下方代码进行了改正。

100分代码

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int MOD=1e9+7;
int n,m,cnt;
string st, str;
struct nodes
{
	int id=-1;
	int l=-1;//该节点左孩子的id 为-1时则表示没有左孩子 
	int r=-1;//该节点右孩子的id 为-1时则表示没有右孩子 
	string po;//该节点自身表示的字符串 
	int d_v;//以该节点为根的子树的表达式的导数的值 
	int pre_v;//以该节点为根的子树的表达式 直接带入各个数值的原始值 
}node[240];
stack<nodes> s;
int x[110];
int xnum;
int qmi(int a,int b)
{
	a=a%MOD;
	b=b%MOD;
	int res=0;
	res=(a*b)%MOD;
	return res;
}
void dfs(int cnt)
{
	if(node[cnt].l==-1&&node[cnt].r==-1)//没有左右孩子是叶子结点 
	{//叶子结点一定是某自变量或常数 

		string temp=node[cnt].po;
		if(temp[0]=='x')//是自变量 
		{
			//int num=temp[1]-'0';
			
			//算出xi的i 
			int num=0;
			for(int i=1;temp[i]!='\0';i++)
			{
				num=num*10+(temp[i]-'0');
			}
			
			if(num==xnum)//是设定的需要求偏导的那个自变量 
			{
				
				node[cnt].d_v=1;
				node[cnt].pre_v=x[num]; 
			}
			else//其他自变量 当做常数处理 
			{
			
				node[cnt].d_v=0;
				node[cnt].pre_v=x[num];
			}
		}
		else//是常数本身 
		{
				node[cnt].d_v=0;
				node[cnt].pre_v=atoi(temp.c_str());
		}
		return;
	}
	else
	{//不是叶子结点一定是某个符号 

		string temp=node[cnt].po;
		dfs(node[cnt].l);//遍历左右,用于下方计算出该结点子树形成的表达式的导数值和原始值 
		dfs(node[cnt].r);

		if(temp=="+")
		{
			node[cnt].pre_v=(node[node[cnt].l].pre_v+node[node[cnt].r].pre_v)%MOD;
			node[cnt].d_v=(node[node[cnt].l].d_v+node[node[cnt].r].d_v)%MOD;
		}
		else if(temp=="-")
		{
			node[cnt].pre_v=node[node[cnt].l].pre_v-node[node[cnt].r].pre_v;
			node[cnt].d_v=node[node[cnt].l].d_v-node[node[cnt].r].d_v;
		}else if(temp=="*")
		{
			int a=node[node[cnt].l].d_v;
			int b=node[node[cnt].l].pre_v;
			int c=node[node[cnt].r].d_v;
			int d=node[node[cnt].r].pre_v;
			node[cnt].pre_v=qmi(b,d);
			node[cnt].d_v=qmi(a,d)+qmi(b,c);
		}
	}
}
signed main()
{
    cin >> n >> m;
    getchar();
    getline(cin, st);
    stringstream ss(st);
    while (getline(ss, str, ' ')) {

		if(str=="+"||str=="-"||str=="*")
		{
			cnt++;//给树的结点编号从1开始 
			node[cnt].po=str;
			node[cnt].id=cnt;
			
			//从栈中取出两个元素 设为其左右孩子
			nodes temp1;
			temp1=s.top();
			s.pop();
			node[cnt].r=temp1.id;
            //注意此处要先设为右孩子,后出栈的设为左孩子
			nodes temp2;
			temp2=s.top();
			s.pop();
			node[cnt].l=temp2.id;

			s.push(node[cnt]);

		}
		else
		{
			cnt++;
			node[cnt].po=str;
			node[cnt].id=cnt;	
			s.push(node[cnt]);		
		}
	}
	//root的id=cnt

	int root=cnt;
	while(m--)
	{
		xnum=0;
		memset(x,0,sizeof(x));
		cin>>xnum;
		for(int i=1;i<=n;i++) cin>>x[i];
		
		dfs(root);//以编号为cnt的结点为根节点遍历每个结点计算导数的值 
		int res=node[root].d_v;
		
		res=res%(MOD);
		if(res>=0)
		{
			cout<<res<<endl;			
		}
		else
		{
			res+=MOD;
			cout<<res<<endl;
		}
	}

	return 0;
}
其他注意点:

        在计算各个结点处对应的导数和原始值的过程中就一边进行对题目要求的MOD进行取模,防止数字过大。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值