CCF刷题计划——梯度求解(map表示公式)

参考【CCF-CSP】 202309-3 梯度求解_ccf202309-3-优快云博客

梯度求解

计算机软件能力认证考试系统

很有启发性的一道题!这道题让我收获了如何用map表示公式:公式,说白了就是多项式。而多项式又是由众多单项式组成的,所以,我们需要先学会表达单项式。单项式形如:cX^e,需要一个值来表示系数,一个值来表示指数,对于只有一个变量的单项式,我们可以有很多种方式来表达。但是,考虑到单项式多了,有些单项式没有整合到一起,比如 3X^2+6X^2 ,相同指数的项是可以合并系数的。为了方便我们直接定位到指数的系数,我们使用 map<int,int> ,分别存储c和e,来表示一个单项式。

x1*x1*x1+x1*3转换为

指数1 系数3

指数3 系数1

在该map中添加多个c,e组合,也就意味着我们添加了多个单项式,这个map就是成为了一个多项式。

为了转化成上面的形式,我们需要提前将那些不重要的变量(也就是将那些不求导的变量)转化为系数(对于不求导的变量而言,这一步可以提前处理,也可以最后处理,都一样,为了方便,我们提前处理),然后再运算。

 AC:

#include <iostream>
#include <sstream>	//用于处理获取空格隔开的字符串 
#include <stack>
#include <string>
#include <vector>
#include <cmath>
#include <map>
using namespace std;
#define ll long long
const int MOD=1e9+7; 
const int N=1e2+2;

/*
公式怎么表示?
	首先,将公式化简。无关的变量全部转化为系数。如x1+x1*x1*x2,求x1的倒数x2=2,那么就为x1+2*x1^2这种
	然后我们需要表示的是单独存在x1的项的系数和指数即可。推荐使用map<ll,ll>,分别表示 指数、系数  
	map外面套一层stack,便于我们对整体公式进行整合 

*/

int n,m;	//自变量个数,要求解的偏导数的个数 
vector<string>vec;
stack<map<ll,ll>>func;

int main()
{
	cin>>n>>m;
	stringstream linestream;
	string line,t;
	getchar();	//getline之前都来杯getchar 
	getline(cin,line);
	linestream.str(line);	//相当于这里将其空格处理 
	while(linestream>>t)	 vec.push_back(t);
	
	int id;
	vector<int>x(n+1,0);	//n个元素 
	while(m--)
	{
		cin>>id;
		for(int i=1;i<=n;i++)	cin>>x[i];
		string goal="x"+to_string(id);	//首先记录下了求导的变量
		for(int i=0;i<vec.size();i++)	//遍历容器 
		{
			t=vec[i];	//t可能是 运算符、xi(xi分为goal和非goal)、常数 
			//在前期,一般只会出现单个的项,如x1,x2这种,此时就是将其转换成系数的最好时机 
			
			if(t==goal)	//如果当前得到的项是要求导的项
			{
				map<ll,ll>mp;
				mp[1]=1;
				func.push(mp);	//直接保留 
			 }
			else if(t[0]=='x')	//没有进入goal的且为x,说明是非goal变量
			{
				//将其转换为系数
//				int tempId=stoi(t[1]);		//字符串转整型:stoi(string str);stol、stoll、stod(double) 
//				上面因为t[1]为char,倒不能直接转,而是先要转为string,用substr 
				int tempId=stoi(t.substr(1));	//substr(size_t pos,size_t len)
				map<ll,ll>mp;
				mp[0]=x[tempId];
				func.push(mp);
			 }
			//上面两种情况只在前期容易出现,也就是单项式的情况。下面将这些单项式逐渐转换为多项式,并进行运算 
			else if(t=="+"||t=="-"||t=="*")	//如果是运算符 
			{
				//会将栈中两个元素弹出,进行运算操作 
				//这里我们用两个map接收两个元素(其实本质是多项式) 
				map<ll,ll>mp2=func.top();	 func.pop();	 
				map<ll,ll>mp1=func.top();	 func.pop();
				map<ll,ll>mp;	//用来表示运算结果 
				
				if(t=="+")
				{
					//加法运算,如 3*x1+5*x1,相同指数的系数相加
					mp=mp1;
					for(auto &it:mp2)	//first是指数,second是系数 
						mp[it.first]=(mp[it.first]+it.second)%MOD;	//该式本质是 同指数下的mp.second+mp2.second 
						
				}
				
				else if(t=="-")	//减法同理 
				{
					mp=mp1;
					for(auto &it:mp2)	//first是指数,second是系数 
						mp[it.first]=(mp[it.first]-it.second)%MOD;
				}
				
				else
				{	//乘法就比较复杂:需要将mp1的所有项都和mp2的所有项相乘
					//而单独两项相乘,就是系数相乘,指数相加
					//就结果而言,结果的指数对应的系数就是两个系数乘后的累加	 
					for(auto &it1:mp1)
						for(auto &it2:mp2)
						{
							mp[it1.first+it2.first]+=it1.second*it2.second;
							mp[it1.first+it2.first]%=MOD; 
						}
				}
				func.push(mp);	//将运算结果多项式放回去 
			}
			else //数字
			{
				ll data=stoll(t);	//转换为longlong
				map<ll,ll>mp;
				mp[0]=data;
				func.push(mp); 
			 } 
		 } 
		
		//最后func中得到的是一个关于goal的多项式
		//接下来我们需要将这个多项式进行求导操作 
		//其实这里就简单了,指数-1,指数为0的舍弃,然后将值带入
		
		ll ans=0; 
		map<ll,ll>f=func.top();
		for(auto &it:f)
		{
			ll e=it.first;	//指数	原指数会-1 
			ll c=it.second;	//系数	原系数会*e 
			if(e==0)	continue;
			ans=(ans+c*e*(ll)pow(x[id],e-1))%MOD;
		}
		
        //下面是链接中大佬给的源代码,我不太理解,所以换成了上面的,也是满分
//		ll ans=0,pree=0,fac=1;
//		map<ll,ll>f=func.top();
//		for(auto &it:f) 
//		{
//			ll e=it.first;	//指数 
//			ll c=it.second;	//系数
//			for(int i=pree+1;i<e;i++)
//				fac=fac*x[id]%MOD;	
//			pree=e==0?0:e-1;	 
//			ans=(ans+c*e*fac)%MOD;
//		}
		cout<<(ans+MOD)%MOD<<endl;	//因为负数也要求在 [0,MOD)的范围内,所以需要+MOD使其变成正数
									//如果本来就是正数也无所谓,+mod后会%去 
	}
	return 0;
 } 

<think>好的,我现在需要帮助用户解决关于CCF CSP考试中梯度求解的问,特别是实现方法。首先,我得仔细看看用户提供的引用内容,了解他们的需求。 用户提到了引用1中的CCF-CSP梯度求解目,提到使用高精度计算和暴力求导,还有数值方法验证解析解。引用2则讨论了如何将表达式转换为系数和指数的形式,方便求导。用户的问是如何在CCF CSP中实现梯度下降算法,或者相关的梯度求解方法。 首先,我需要确认梯度下降算法是否直接适用于CCF CSP的目。但根据用户提供的引用,目可能更偏向于符号求导而不是梯度下降的优化过程。例如,202309-3是关于计算梯度(即导数)的值,而不是使用梯度下降法来优化模型。因此,用户可能需要的是如何实现自动求导或符号求导,而不是梯度下降算法本身。这可能是一个需要澄清的地方,但根据用户的问,他们可能混淆了这两个概念。 接下来,我需要整理实现梯度求解的关键步骤。根据引用2中的内容,表达式需要被解析并转换为可以处理的结构,比如系数和指数的组合。例如,将x1^3 + 3x1转换为多项式的形式,然后对每个变量求导。这可能涉及到将表达式分解为多个项,每个项由变量的指数和系数组成,然后应用求导规则。 另外,引用1提到使用数值方法来验证解析解的正确性,这可以用于测试实现的正确性。例如,使用中心差分法计算数值导数,然后与解析解对比。 现在,我需要将这些思路转化为具体的实现步骤。首先,解析输入的表达式,将其转换为结构化的数据,比如存储每个项的系数和变量指数。例如,对于x1^3,系数是1,x1的指数是3。然后,对于每个变量求导时,处理每个项,将系数乘以指数,指数减一,如果指数为0则该项消失。 可能的挑战包括处理多变量的情况,比如表达式中有x1、x2等,需要分别对每个变量求导。此外,高精度计算可能涉及到大数处理,但用户提到千位高精,可能需要使用字符串或数组来存储大数,避免溢出。 然后,根据这些步骤,可以给出一个代码框架。例如,定义一个Term类,包含变量的指数和系数,然后解析表达式为Term列表。在求导时,遍历每个Term,应用求导规则,合并同类项,最后计算结果。 用户可能需要具体的代码示例,但根据问,可能更关注算法步骤而不是完整代码。因此,在回答中应分步骤解释,并给出关键代码片段,如解析表达式、求导过程、数值验证等。 最后,生成的相关问需要围绕梯度求解的实现细节、优化方法、高精度处理等,帮助用户进一步理解。</think>针对CCF CSP考试中梯度求解的实现方法,以下是关键步骤解析: ### 一、问理解与算法选择 CCF CSP梯度求解(如202309-3)要求对复杂表达式进行自动微分。推荐采用**符号求导法**而非数值近似法,因其时间复杂度$O(n)$更优[^1]。核心思路是将表达式解析为计算树,按求导规则逐层推导。 ### 二、实现步骤 #### 1. 表达式解析 将输入表达式转换为计算树结构: ```python class Node: def __init__(self, op, left=None, right=None, val=None): self.op = op # 运算符/变量名 self.left = left self.right = right self.val = val # 常数值 ``` #### 2. 自动微分实现 对每个变量$x_i$分别求导,递归应用求导规则: $$ \frac{\partial}{\partial x}(u+v) = \frac{\partial u}{\partial x} + \frac{\partial v}{\partial x} $$ $$ \frac{\partial}{\partial x}(uv) = u\frac{\partial v}{\partial x} + v\frac{\partial u}{\partial x} $$ #### 3. 高精度计算 使用字符串存储超过1000位的数值: ```python def add_big_num(a: str, b: str) -> str: max_len = max(len(a), len(b)) a = a.zfill(max_len) b = b.zfill(max_len) result = [] carry = 0 # 逐位计算逻辑... ``` ### 三、代码框架示例 ```python def compute_gradient(expr, vars_dict, target_var): root = build_ast(expr) # 构建计算树 derivatives = {} for var in vars_dict: # 对每个变量求导 derivatives[var] = evaluate_derivative(root, var) return derivatives[target_var] def evaluate_derivative(node, var): if node.is_constant(): return 0 if node.is_variable(): return 1 if node.name == var else 0 # 应用求导规则... ``` ### 四、验证方法 使用数值梯度检验解析解: $$ f'(x) \approx \frac{f(x+\epsilon)-f(x-\epsilon)}{2\epsilon} $$ 取$\epsilon=10^{-5}$可验证实现正确性
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值