[题解]P7077 [CSP-S2020] 函数调用
题意简述
给定一个长度为\(n\)的序列\(a_1,a_2,\dots,a_n\),给定\(m\)个函数,每个函数可能是下面\(3\)种类型,用\(T_x\)表示函数\(x\)的类型:
- \(T_x =1\),对下标\(p\)增加\(v\)。
- \(T_x =2\),对所有元素乘\(v\)。
- \(T_x =3\),由若干类型\(1\)和类型\(2\)组成的复合函数,按顺序执行。
给定初始序列和每个函数的定义,最后给定一个调用序列,你需要按顺序调用完该序列所有函数后输出序列。
思路分析
我们把函数看作节点,调用关系看作有向边(调用连向被调用),因为没有递归调用,所以这是一个DAG(有向无环图)。
由于乘法是区间的,所以最终每个\(a_i\)都可以表示为\(a_i\times k+b_i\)。其中\(k\)就是函数序列中每个函数的\(mul\)值之积,其中\(mul_i\)的定义如下:
- \(i\)是类型\(1\)函数:\(mul_i=1\)。
- \(i\)是类型\(2\)函数:\(mul_i=v\)。
- \(i\)是类型\(3\)函数:\(mul_i=\prod\limits_{i\rightarrow j} mul_j\)。
这样\(k\)就计算出来了。
我们接下来要计算\(b_i\),那么需要知道每个\(1\)类函数被执行了多少次,我们记第\(1\)类函数\(x\)被执行的次数为\(cnt_x\),那么\(b_i=\sum\limits_{T_{_j}=1,p_{_j}=i}v_j\times cnt_j\)。
先考虑没有类型\(2\)函数的情况,我们只需要对于调用的每个函数\(i\),将\(cnt_i \leftarrow cnt_i +1\),然后再DAG上递推,把父节点的\(cnt\)推给子节点即可。最后对于每个叶子结点(类型\(1,2\)的函数)中类型为\(1\)的函数\(i\),将\(cnt_i\times v_i\)加给\(p_i\)即可。
如果有类型\(2\)函数,我们推给子节点的\(cnt\)可能会乘上一个系数,假设我们当前遍历到节点\(u\),有\(s\)个子节点,对于第\(i\)个子节点\(son_i\),\(u\)对\(son_i\)的贡献是\(cnt_u\times cur\),其中\(cur=\prod\limits_{j=i+1}^{s} mul_i\)。这是因为后面函数的“乘”操作会影响到前面的函数。所以我们可以倒序遍历子节点,累乘贡献即可。
相应地,主函数中为\(sum\)赋初值也需要累乘贡献。
也可以这样思考:序列初始为\(0\),第\(i\)位增加\(a_i\)作为\(n\)个函数额外放在其他函数前执行。这样输出就不用管\(a_i\times k\)了,只要输出\(b_i\)即可。
Code
点击查看代码
#include<bits/stdc++.h> #define N 100010 #define mod 998244353 #define int long long using namespace std; int n,m,q,a[N],deg[N],fun[N]; int p[N],v[N];//存储叶子节点(操作1,2)的信息 int ord[N],tn;//ord[i]表示拓扑序为i的节点,tn为计数器 int mul[N];//mul[i]表示函数i要乘多少 int cnt[N];//cnt[i]表示函数i要执行多少次 vector<int> G[N]; queue<int> que; void topo(){//预处理出拓扑序,方便转移 for(int i=1;i<=m;i++) if(!deg[i]) que.push(i); while(!que.empty()){ int u=que.front(); que.pop(),ord[++tn]=u; for(int i:G[u]){ deg[i]--; if(!deg[i]) que.push(i); } } } void getmul(){//自下而上计算节点的mul for(int i=m;i>=1;i--){ int u=ord[i]; for(int j:G[u]) mul[u]=mul[u]*mul[j]%mod; } } void getsum(){//自上而下计算节点的sum for(int i=1;i<=m;i++){ int u=ord[i],cur=cnt[u]; for(int j:G[u]) cnt[j]=(cnt[j]+cur%mod)%mod, cur=cur*mul[j]%mod; } } signed main(){ ios::sync_with_stdio(false); cin.tie(nullptr),cout.tie(nullptr); cin>>n; for(int i=1;i<=n;i++) cin>>a[i]; cin>>m; for(int i=1;i<=m;i++){ int op; cin>>op; if(op==1){ cin>>p[i]>>v[i]; mul[i]=1; }else if(op==2){ cin>>v[i]; mul[i]=v[i]; }else{ int c,v; cin>>c; mul[i]=1; while(c--){ cin>>v; G[i].emplace_back(v); deg[v]++; } } } for(int i=1;i<=m;i++) reverse(G[i].begin(),G[i].end());//倒序遍历 topo(); getmul(); cin>>q; int cur=1; for(int i=1;i<=q;i++) cin>>fun[i]; for(int i=q;i>=1;i--){ int u=fun[i]; cnt[u]=(cnt[u]+cur)%mod; cur=cur*mul[u]%mod; } getsum(); for(int i=1;i<=n;i++) a[i]=a[i]*cur%mod; for(int i=1;i<=m;i++){ if(p[i]) a[p[i]]=(a[p[i]]+v[i]*cnt[i]%mod)%mod; } for(int i=1;i<=n;i++) cout<<a[i]<<" "; return 0; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效